Emulation's Evo Client

Really advanced tracers for evoworld.io

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Emulation's Evo Client
// @description  Really advanced tracers for evoworld.io
// @match        evoworld.io
// @version      8.4
// @license      MIT
// @namespace https://greasyfork.org/users/1481831
// ==/UserScript==

(function() {
  'use strict';

  document.title = "Emulation's Evo Client Enhanced";

  // Wait for game objects to load
  function waitForGameLoad() {
    if (typeof game !== 'undefined' && game.canvas && game.me) {
      initModMenu();
    } else {
      setTimeout(waitForGameLoad, 100);
    }
  }

  function initModMenu() {
    try {
      const canvas = game.canvas;
      canvas.style.cursor = 'default';

      // Configuration object
      const config = {
        showFood: false,
        showEnemies: false,
        showAllObjects: false,
        highlightEnemies: false,
        showEnemyNames: false,
        showHealthBars: false,
        showResourceIndicators: false,
        showThreatLevel: false,
        showDistanceOpacity: false,
        showHiddenObjects: false,
        showTeamIndicators: false,
        showMiniMap: false,
        showVelocityArrows: false,
        showHitboxOutlines: false,
        showLevelIndicators: false,
        showStatusEffects: false,
        showPredatorESP: false,
        showPreyESP: false,
        showSafeZones: false,
        showGridOverlay: false,
        showESPBoxes: false,
        showPlayerPaths: false,
        tracerLength: 1.0, // 0.5 to 2.0
        tracerThickness: 1.5, // 0.5 to 5.0
        tracerGlow: false,
        tracerColor: '#ffffff' // Default white
      };

      // Create mod menu container
      const menu = document.createElement('div');
      Object.assign(menu.style, {
        position: 'fixed',
        top: '10px',
        right: '10px',
        width: '400px',
        maxHeight: '95vh',
        background: 'linear-gradient(135deg, rgba(20, 20, 20, 0.95), rgba(40, 40, 40, 0.95))',
        color: '#ffffff',
        borderRadius: '12px',
        fontFamily: '"Montserrat", sans-serif',
        zIndex: 10000,
        display: 'none',
        boxShadow: '0 4px 20px rgba(0, 255, 128, 0.4)',
        overflow: 'hidden',
        border: '1px solid rgba(0, 255, 128, 0.5)'
      });
      document.body.appendChild(menu);

      // CSS
      const style = document.createElement('style');
      style.textContent = `
        @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap');
        .mod-menu * { box-sizing: border-box; }
        .mod-menu { font-size: 14px; }
        .mod-menu input, .mod-menu select {
          background: #222;
          color: #fff;
          border: 1px solid #00ff80;
          border-radius: 6px;
          padding: 6px;
          transition: all 0.3s ease;
        }
        .mod-menu input:focus, .mod-menu select:focus {
          outline: none;
          border-color: #00cc66;
          background: #333;
        }
        .mod-menu .content {
          max-height: calc(95vh - 70px);
          overflow-y: auto;
          padding: 15px;
          scrollbar-width: thin;
          scrollbar-color: #00ff80 #222;
        }
        .mod-menu .content::-webkit-scrollbar {
          width: 10px;
        }
        .mod-menu .content::-webkit-scrollbar-track {
          background: #222;
        }
        .mod-menu .content::-webkit-scrollbar-thumb {
          background: #00ff80;
          border-radius: 5px;
        }
        .mod-menu .title-bar {
          background: #1a1a1a;
          padding: 10px;
          display: flex;
          justify-content: space-between;
          align-items: center;
          border-bottom: 1px solid #00ff80;
        }
        .mod-menu .title {
          font-size: 18px;
          font-weight: 700;
          color: #00ff80;
        }
        .mod-menu .close-btn {
          font-size: 24px;
          cursor: pointer;
          color: #fff;
          transition: color 0.3s ease;
        }
        .mod-menu .close-btn:hover {
          color: #00ff80;
        }
        .mod-menu .section-header {
          font-size: 16px;
          font-weight: 600;
          color: #00ff80;
          margin: 15px 0 10px;
          padding: 5px 0 5px 10px;
          cursor: pointer;
          text-transform: uppercase;
          border-left: 3px solid #00ff80;
        }
        .mod-menu .section-header:hover {
          color: #00cc66;
        }
        .mod-menu .section-content {
          display: none;
          padding-left: 10px;
        }
        .mod-menu .section-content.active {
          display: block;
        }
        .mod-menu .separator {
          border-top: 1px solid #444;
          margin: 10px 0;
        }
        .mod-menu .mod-item {
          display: flex;
          align-items: center;
          margin-bottom: 12px;
          gap: 10px;
        }
        .mod-menu .mod-label {
          flex: 1;
          font-size: 14px;
        }
        .mod-menu .toggle-btn {
          background: #ff3333;
          color: #fff;
          border: 1px solid #00ff80;
          border-radius: 6px;
          padding: 6px 12px;
          cursor: pointer;
          font-size: 12px;
          font-weight: 600;
          transition: all 0.3s ease;
          min-width: 60px;
          text-align: center;
        }
        .mod-menu .toggle-btn.on {
          background: #00ff80;
          color: #000;
        }
        .mod-menu .toggle-btn:hover {
          background: #00cc66;
          border-color: #00cc66;
        }
        .mod-menu .range-input {
          width: 100px;
        }
        .mod-menu .color-input {
          width: 60px;
          height: 26px;
          padding: 2px;
          cursor: pointer;
        }
        .mod-menu .notification {
          position: fixed;
          bottom: 20px;
          left: 20px;
          background: rgba(34, 34, 34, 0.95);
          color: #fff;
          padding: 12px 18px;
          border-radius: 8px;
          border: 1px solid #00ff80;
          opacity: 0;
          transform: translateY(30px);
          transition: all 0.3s ease;
          zIndex: 10001;
          font-size: 14px;
        }
        .mod-menu .notification.show {
          opacity: 1;
          transform: translateY(0);
        }
        .mod-menu .mini-map {
          position: fixed;
          bottom: 15px;
          left: 15px;
          width: 200px;
          height: 200px;
          background: rgba(0, 0, 0, 0.8);
          border: 2px solid #00ff80;
          border-radius: 8px;
          zIndex: 9999;
        }
      `;
      document.head.appendChild(style);

      // Mod menu HTML
      menu.innerHTML = `
        <div class="mod-menu">
          <div class="title-bar">
            <h2 class="title">Emulation's Evo Client Enhanced</h2>
            <button class="close-btn">&times;</button>
          </div>
          <div class="content">
            <div class="section-header" data-section="esp">ESP Features</div>
            <div class="section-content" id="esp-section">
              <div class="mod-item">
                <span class="mod-label">ESP Food</span>
                <button class="toggle-btn" id="showFood">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Enemies</span>
                <button class="toggle-btn" id="showEnemies">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP All Objects</span>
                <button class="toggle-btn" id="showAllObjects">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Predators</span>
                <button class="toggle-btn" id="showPredatorESP">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Prey</span>
                <button class="toggle-btn" id="showPreyESP">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Hidden Objects</span>
                <button class="toggle-btn" id="showHiddenObjects">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Bounding Boxes</span>
                <button class="toggle-btn" id="showESPBoxes">OFF</button>
              </div>
            </div>
            <div class="section-header" data-section="indicators">Indicators</div>
            <div class="section-content" id="indicators-section">
              <div class="mod-item">
                <span class="mod-label">Enemy Name Tags</span>
                <button class="toggle-btn" id="showEnemyNames">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Health Bars</span>
                <button class="toggle-btn" id="showHealthBars">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Resource Indicators</span>
                <button class="toggle-btn" id="showResourceIndicators">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Threat Level</span>
                <button class="toggle-btn" id="showThreatLevel">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Team Indicators</span>
                <button class="toggle-btn" id="showTeamIndicators">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Level Indicators</span>
                <button class="toggle-btn" id="showLevelIndicators">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Status Effects</span>
                <button class="toggle-btn" id="showStatusEffects">OFF</button>
              </div>
            </div>
            <div class="section-header" data-section="overlays">Visual Overlays</div>
            <div class="section-content" id="overlays-section">
              <div class="mod-item">
                <span class="mod-label">Show Mini-Map</span>
                <button class="toggle-btn" id="showMiniMap">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Velocity Arrows</span>
                <button class="toggle-btn" id="showVelocityArrows">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Hitbox Outlines</span>
                <button class="toggle-btn" id="showHitboxOutlines">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Safe Zones</span>
                <button class="toggle-btn" id="showSafeZones">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">Grid Overlay</span>
                <button class="toggle-btn" id="showGridOverlay">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">Player Path Prediction</span>
                <button class="toggle-btn" id="showPlayerPaths">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">ESP Distance Opacity</span>
                <button class="toggle-btn" id="showDistanceOpacity">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">Highlight Enemies</span>
                <button class="toggle-btn" id="highlightEnemies">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">Tracer Length</span>
                <input type="range" id="tracerLength" min="0.5" max="2.0" step="0.1" value="1.0" class="range-input">
              </div>
              <div class="mod-item">
                <span class="mod-label">Tracer Thickness</span>
                <input type="range" id="tracerThickness" min="0.5" max="5.0" step="0.5" value="1.5" class="range-input">
              </div>
              <div class="mod-item">
                <span class="mod-label">Tracer Glow Effect</span>
                <button class="toggle-btn" id="tracerGlow">OFF</button>
              </div>
              <div class="mod-item">
                <span class="mod-label">Tracer Color</span>
                <input type="color" id="tracerColor" value="#ffffff" class="color-input">
              </div>
            </div>
          </div>
          <div id="notifications"></div>
        </div>
      `;

      // Notification system
      let lastNotification = { message: '', time: 0 };
      function showNotification(message, duration = 3000) {
        if (message === lastNotification.message && Date.now() - lastNotification.time < 5000) return;
        lastNotification = { message, time: Date.now() };
        const notification = document.createElement('div');
        notification.className = 'notification';
        notification.textContent = message;
        document.getElementById('notifications').appendChild(notification);
        setTimeout(() => notification.classList.add('show'), 100);
        setTimeout(() => {
          notification.classList.remove('show');
          setTimeout(() => notification.remove(), 300);
        }, duration);
      }

      // Toggle menu with semicolon
      document.addEventListener('keydown', (e) => {
        if (e.key === ';') {
          e.preventDefault();
          menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
          showNotification(`Menu ${menu.style.display === 'block' ? 'opened' : 'closed'}`);
        }
      });
      menu.querySelector('.close-btn').addEventListener('click', () => {
        menu.style.display = 'none';
        showNotification('Menu closed');
      });

      // Collapsible sections
      menu.querySelectorAll('.section-header').forEach(header => {
        header.addEventListener('click', () => {
          const section = header.dataset.section;
          const content = document.getElementById(`${section}-section`);
          const isActive = content.classList.contains('active');
          menu.querySelectorAll('.section-content').forEach(c => c.classList.remove('active'));
          menu.querySelectorAll('.section-header').forEach(h => h.style.color = '#00ff80');
          if (!isActive) {
            content.classList.add('active');
            header.style.color = '#00cc66';
          }
        });
      });

      // Event listeners for toggle buttons and inputs
      const configKeys = [
        'showFood', 'showEnemies', 'showAllObjects', 'highlightEnemies', 'showEnemyNames', 'showHealthBars',
        'showResourceIndicators', 'showThreatLevel', 'showDistanceOpacity', 'showHiddenObjects',
        'showTeamIndicators', 'showMiniMap', 'showVelocityArrows', 'showHitboxOutlines',
        'showLevelIndicators', 'showStatusEffects', 'showPredatorESP', 'showPreyESP',
        'showSafeZones', 'showGridOverlay', 'showESPBoxes', 'showPlayerPaths', 'tracerGlow'
      ];
      configKeys.forEach(id => {
        const button = document.getElementById(id);
        if (button) {
          button.textContent = config[id] ? 'ON' : 'OFF';
          button.classList.toggle('on', config[id]);
          button.addEventListener('click', () => {
            config[id] = !config[id];
            button.textContent = config[id] ? 'ON' : 'OFF';
            button.classList.toggle('on', config[id]);
            showNotification(`${id.replace(/([A-Z])/g, ' $1').trim()} ${config[id] ? 'enabled' : 'disabled'}`);
          });
        }
      });

      // Event listeners for range and color inputs
      const tracerLengthInput = document.getElementById('tracerLength');
      if (tracerLengthInput) {
        tracerLengthInput.value = config.tracerLength;
        tracerLengthInput.addEventListener('input', () => {
          config.tracerLength = parseFloat(tracerLengthInput.value);
          showNotification(`Tracer Length set to ${config.tracerLength.toFixed(1)}`);
        });
      }

      const tracerThicknessInput = document.getElementById('tracerThickness');
      if (tracerThicknessInput) {
        tracerThicknessInput.value = config.tracerThickness;
        tracerThicknessInput.addEventListener('input', () => {
          config.tracerThickness = parseFloat(tracerThicknessInput.value);
          showNotification(`Tracer Thickness set to ${config.tracerThickness.toFixed(1)}`);
        });
      }

      const tracerColorInput = document.getElementById('tracerColor');
      if (tracerColorInput) {
        tracerColorInput.value = config.tracerColor;
        tracerColorInput.addEventListener('input', () => {
          config.tracerColor = tracerColorInput.value;
          showNotification(`Tracer Color set to ${config.tracerColor}`);
        });
      }

      // Canvas context
      function getCanvasContext() {
        try {
          return game.dynamicContext || game.canvas.getContext('2d') || document.getElementsByTagName('canvas')[0]?.getContext('2d') || null;
        } catch (e) {
          console.error('Canvas context error:', e);
          showNotification(`Canvas error: ${e.message}`);
          return null;
        }
      }

      // Get game objects
      function getGameObjects() {
        try {
          return Object.values(game.gameObjects || game.entities || game.players || {});
        } catch (e) {
          console.error('Error accessing game objects:', e);
          showNotification(`Objects error: ${e.message}`);
          return [];
        }
      }

      // Object validation
      function validObj(obj) {
        try {
          return obj && obj.position && typeof obj.position.x === 'number' && typeof obj.position.y === 'number' &&
                 (obj.active ?? obj.isActive ?? true);
        } catch (e) {
          console.error('Object validation error:', e);
          return false;
        }
      }

      // Food chain check
      function canEat(a, b) {
        try {
          return window.foodChain?.[a?.name]?.eats?.[b?.name] || b?.type === 'food';
        } catch (e) {
          console.error('Food chain error:', e);
          return false;
        }
      }

      // ESP Drawing
      function drawESP() {
        try {
          const ctx = getCanvasContext();
          if (!ctx) return;

          const me = game.me;
          if (!me || !me.position || typeof me.position.x !== 'number' || typeof me.position.y !== 'number') {
            showNotification('Error: Player position invalid');
            return;
          }

          ctx.save();
          ctx.imageSmoothingEnabled = true;

          const meX = me.position.x + (me.width || 20) / 2;
          const meY = me.position.y + (me.height || 20) / 2;

          const getRenderPosition = (x, y) => {
            try {
              if (game.getRenderPosition) return game.getRenderPosition(x, y);
              const scale = game.camera?.scale || 1;
              const camX = game.camera?.x || meX;
              const camY = game.camera?.y || meY;
              return {
                x: (x - camX) * scale + canvas.width / 2,
                y: (y - camY) * scale + canvas.height / 2
              };
            } catch (e) {
              console.error('Render position error:', e);
              showNotification('Error in render position');
              return { x: canvas.width / 2, y: canvas.height / 2 };
            }
          };

          // Draw grid overlay
          if (config.showGridOverlay) {
            const scale = game.camera?.scale || 1;
            const camX = game.camera?.x || meX;
            const camY = game.camera?.y || meY;
            const gridSize = 100;
            ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
            ctx.lineWidth = 1;
            for (let x = -10000; x <= 10000; x += gridSize) {
              const renderX = (x - camX) * scale + canvas.width / 2;
              ctx.beginPath();
              ctx.moveTo(renderX, 0);
              ctx.lineTo(renderX, canvas.height);
              ctx.stroke();
            }
            for (let y = -5000; y <= 5000; y += gridSize) {
              const renderY = (y - camY) * scale + canvas.height / 2;
              ctx.beginPath();
              ctx.moveTo(0, renderY);
              ctx.lineTo(canvas.width, renderY);
              ctx.stroke();
            }
          }

          const objects = getGameObjects();
          for (const obj of objects) {
            if (!validObj(obj)) continue;

            const objX = obj.position.x + (obj.width || 20) / 2;
            const objY = obj.position.y + (obj.height || 20) / 2;
            const dx = meX - objX;
            const dy = meY - objY;
            const dist = Math.sqrt(dx * dx + dy * dy);
            const meRender = getRenderPosition(meX, meY);
            const objRender = getRenderPosition(objX, objY);

            const isFood = canEat(me, obj);
            const isEnemy = canEat(obj, me) || obj.type === 'player';
            const isResource = obj.type === 'water' || obj.type === 'oxygen';
            const isHidden = obj.isHidden || obj.hidden || obj.opacity < 1;
            const isTeam = obj.team && me.team && obj.team === me.team && obj !== me;
            const isPredator = canEat(obj, me);
            const isPrey = canEat(me, obj);

            // ESP Tracers
            let shouldDrawTracer = false;
            let baseColor = config.tracerColor;
            if (config.showFood && isFood) {
              shouldDrawTracer = true;
              baseColor = '#00ff00';
            } else if (config.showEnemies && isEnemy) {
              shouldDrawTracer = true;
              baseColor = '#ff3333';
            } else if (config.showAllObjects && !isResource) {
              shouldDrawTracer = true;
              baseColor = '#ffff00';
            } else if ((config.showResourceIndicators || config.showAllObjects) && isResource) {
              shouldDrawTracer = true;
              baseColor = '#0000ff';
            } else if (config.showHiddenObjects && isHidden) {
              shouldDrawTracer = true;
              baseColor = '#ff00ff';
            } else if (config.showPredatorESP && isPredator) {
              shouldDrawTracer = true;
              baseColor = '#ff0000';
            } else if (config.showPreyESP && isPrey) {
              shouldDrawTracer = true;
              baseColor = '#00ff80';
            }

            if (shouldDrawTracer) {
              const opacity = config.showDistanceOpacity ? Math.max(0.3, 1 - dist / 3000) : 1;
              ctx.strokeStyle = baseColor.replace(')', `, ${opacity})`).replace('rgb', 'rgba');
              ctx.lineWidth = config.tracerThickness;
              if (config.tracerGlow) {
                ctx.shadowColor = baseColor;
                ctx.shadowBlur = 10;
              }
              ctx.beginPath();
              const tracerEndX = meRender.x + (objRender.x - meRender.x) * config.tracerLength;
              const tracerEndY = meRender.y + (objRender.y - meRender.y) * config.tracerLength;
              ctx.moveTo(meRender.x, meRender.y);
              ctx.lineTo(tracerEndX, tracerEndY);
              ctx.stroke();
              ctx.shadowBlur = 0;

              ctx.fillStyle = baseColor;
              ctx.font = '16px Montserrat';
              ctx.textAlign = 'center';
              ctx.fillText(`${Math.round(dist)}m`, objRender.x, objRender.y - 20);
              ctx.beginPath();
              ctx.arc(objRender.x, objRender.y, 6, 0, 2 * Math.PI);
              ctx.fill();
            }

            // ESP Bounding Boxes
            if (config.showESPBoxes && (isEnemy || isFood || isResource)) {
              ctx.strokeStyle = baseColor || '#ffffff';
              ctx.lineWidth = 2;
              ctx.strokeRect(
                objRender.x - (obj.width || 20) / 2 * (game.camera?.scale || 1),
                objRender.y - (obj.height || 20) / 2 * (game.camera?.scale || 1),
                (obj.width || 20) * (game.camera?.scale || 1),
                (obj.height || 20) * (game.camera?.scale || 1)
              );
            }

            // Highlight Enemies
            if (config.highlightEnemies && isEnemy) {
              ctx.strokeStyle = '#ff3333';
              ctx.lineWidth = 5;
              ctx.beginPath();
              ctx.arc(objRender.x, objRender.y, (obj.width || 20) / 2 * (game.camera?.scale || 1) + 3, 0, 2 * Math.PI);
              ctx.stroke();
            }

            // Enemy Name Tags
            if (config.showEnemyNames && isEnemy && obj.nick) {
              ctx.fillStyle = '#ff3333';
              ctx.font = '14px Montserrat';
              ctx.textAlign = 'center';
              ctx.fillText(obj.nick, objRender.x, objRender.y - 35);
            }

            // ESP Health Bars
            if (config.showHealthBars && (isEnemy || isFood) && obj.hp && obj.maxHealth) {
              const healthRatio = obj.hp / obj.maxHealth;
              const barWidth = 60;
              const barHeight = 6;
              ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
              ctx.fillRect(objRender.x - barWidth / 2, objRender.y - 30, barWidth, barHeight);
              ctx.fillStyle = '#ff0000';
              ctx.fillRect(objRender.x - barWidth / 2, objRender.y - 30, barWidth * healthRatio, barHeight);
            }

            // ESP Resource Indicators
            if (config.showResourceIndicators && isResource) {
              ctx.fillStyle = '#0000ff';
              ctx.font = '16px Montserrat';
              ctx.textAlign = 'center';
              const label = obj.type === 'water' ? 'Water' : 'Oxygen';
              ctx.fillText(label, objRender.x, objRender.y - 25);
            }

            // ESP Threat Level
            if (config.showThreatLevel && isEnemy) {
              const threat = Math.min(1, (obj.level || 1) / (me.level || 1));
              ctx.fillStyle = threat > 0.5 ? '#ff0000' : '#ffcc00';
              ctx.font = '14px Montserrat';
              ctx.textAlign = 'center';
              ctx.fillText(`Threat: ${Math.round(threat * 100)}%`, objRender.x, objRender.y + 30);
            }

            // ESP Team Indicators
            if (config.showTeamIndicators && isTeam) {
              ctx.fillStyle = '#00ff99';
              ctx.font = '14px Montserrat';
              ctx.textAlign = 'center';
              ctx.fillText('Team', objRender.x, objRender.y + 20);
            }

            // ESP Velocity Arrows
            if (config.showVelocityArrows && (obj.velocityX || obj.velocityY)) {
              const scale = game.camera?.scale || 1;
              const vx = (obj.velocityX || 0) * scale * 15;
              const vy = (obj.velocityY || 0) * scale * 15;
              const magnitude = Math.sqrt(vx * vx + vy * vy);
              if (magnitude > 0) {
                ctx.strokeStyle = '#00ffff';
                ctx.lineWidth = 3;
                ctx.beginPath();
                ctx.moveTo(objRender.x, objRender.y);
                ctx.lineTo(objRender.x + vx, objRender.y + vy);
                ctx.stroke();
                const angle = Math.atan2(vy, vx);
                ctx.beginPath();
                ctx.moveTo(objRender.x + vx, objRender.y + vy);
                ctx.lineTo(objRender.x + vx - 12 * Math.cos(angle - Math.PI / 6), objRender.y + vy - 12 * Math.sin(angle - Math.PI / 6));
                ctx.lineTo(objRender.x + vx - 12 * Math.cos(angle + Math.PI / 6), objRender.y + vy - 12 * Math.sin(angle + Math.PI / 6));
                ctx.closePath();
                ctx.fillStyle = '#00ffff';
                ctx.fill();
              }
            }

            // ESP Hitbox Outlines
            if (config.showHitboxOutlines) {
              ctx.strokeStyle = '#ff9900';
              ctx.lineWidth = 3;
              ctx.strokeRect(
                objRender.x - (obj.width || 20) / 2 * (game.camera?.scale || 1),
                objRender.y - (obj.height || 20) / 2 * (game.camera?.scale || 1),
                (obj.width || 20) * (game.camera?.scale || 1),
                (obj.height || 20) * (game.camera?.scale || 1)
              );
            }

            // ESP Level Indicators
            if (config.showLevelIndicators && (obj.level || obj.xpValue)) {
              ctx.fillStyle = '#ffffff';
              ctx.font = '14px Montserrat';
              ctx.textAlign = 'center';
              const label = obj.level ? `Level: ${obj.level}` : `XP: ${obj.xpValue || 0}`;
              ctx.fillText(label, objRender.x, objRender.y + 40);
            }

            // ESP Status Effects
            if (config.showStatusEffects && obj.statusEffects) {
              const effects = Object.keys(obj.statusEffects || {}).filter(effect => obj.statusEffects[effect]);
              if (effects.length > 0) {
                ctx.fillStyle = '#9900ff';
                ctx.font = '14px Montserrat';
                ctx.textAlign = 'center';
                ctx.fillText(effects.join(', '), objRender.x, objRender.y + 50);
              }
            }

            // ESP Safe Zones
            if (config.showSafeZones && obj.type === 'safeZone') {
              ctx.fillStyle = 'rgba(0, 255, 0, 0.3)';
              ctx.beginPath();
              ctx.arc(objRender.x, objRender.y, (obj.width || 50) / 2 * (game.camera?.scale || 1), 0, 2 * Math.PI);
              ctx.fill();
              ctx.fillStyle = '#00ff00';
              ctx.font = '14px Montserrat';
              ctx.textAlign = 'center';
              ctx.fillText('Safe Zone', objRender.x, objRender.y);
            }

            // Player Path Prediction
            if (config.showPlayerPaths && isEnemy && (obj.velocityX || obj.velocityY)) {
              const scale = game.camera?.scale || 1;
              const vx = (obj.velocityX || 0) * scale * 50;
              const vy = (obj.velocityY || 0) * scale * 50;
              ctx.strokeStyle = 'rgba(255, 255, 0, 0.5)';
              ctx.lineWidth = 2;
              ctx.setLineDash([5, 5]);
              ctx.beginPath();
              ctx.moveTo(objRender.x, objRender.y);
              ctx.lineTo(objRender.x + vx, objRender.y + vy);
              ctx.stroke();
              ctx.setLineDash([]);
            }
          }

          ctx.restore();
        } catch (e) {
          console.error('ESP drawing error:', e);
          showNotification(`ESP error: ${e.message}`);
        }
      }

      // Mini-Map
      let miniMapCanvas = null;
      function drawMiniMap() {
        try {
          if (!config.showMiniMap) {
            if (miniMapCanvas) {
              miniMapCanvas.remove();
              miniMapCanvas = null;
            }
            return;
          }
          if (!miniMapCanvas) {
            miniMapCanvas = document.createElement('canvas');
            miniMapCanvas.className = 'mini-map';
            document.body.appendChild(miniMapCanvas);
          }
          const ctx = miniMapCanvas.getContext('2d');
          miniMapCanvas.width = 200;
          miniMapCanvas.height = 200;
          ctx.clearRect(0, 0, 200, 200);
          ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
          ctx.fillRect(0, 0, 200, 200);
          ctx.strokeStyle = '#00ff80';
          ctx.lineWidth = 2;
          ctx.strokeRect(0, 0, 200, 200);

          const me = game.me;
          if (!me || !me.position) return;

          const mapScale = 0.015;
          const mapCenterX = 100;
          const mapCenterY = 100;

          ctx.fillStyle = '#00ccff';
          ctx.beginPath();
          ctx.arc(mapCenterX, mapCenterY, 5, 0, 2 * Math.PI);
          ctx.fill();

          const objects = getGameObjects();
          for (const obj of objects) {
            if (!validObj(obj)) continue;
            const objX = obj.position.x * mapScale + mapCenterX - me.position.x * mapScale;
            const objY = obj.position.y * mapScale + mapCenterY - me.position.y * mapScale;
            if (Math.abs(objX - mapCenterX) > 100 || Math.abs(objY - mapCenterY) > 100) continue;

            const isFood = canEat(me, obj);
            const isEnemy = canEat(obj, me) || obj.type === 'player';
            const isResource = obj.type === 'water' || obj.type === 'oxygen';
            const isHidden = obj.isHidden || obj.hidden || obj.opacity < 1;
            const isTeam = obj.team && me.team && obj.team === me.team && obj !== me;
            const isPredator = canEat(obj, me);
            const isPrey = canEat(me, obj);
            ctx.fillStyle = isResource ? '#0000ff' :
                           isFood ? '#00ff00' :
                           isEnemy ? '#ff3333' :
                           isHidden ? '#ff00ff' :
                           isTeam ? '#00ff99' :
                           isPredator ? '#ff0000' :
                           isPrey ? '#00ff80' :
                           '#ffff00';
            ctx.beginPath();
            ctx.arc(objX, objY, isEnemy ? 4 : 3, 0, 2 * Math.PI);
            ctx.fill();

            // Mini-Map Velocity Arrows
            if (config.showVelocityArrows && (obj.velocityX || obj.velocityY)) {
              const vx = (obj.velocityX || 0) * mapScale * 60;
              const vy = (obj.velocityY || 0) * mapScale * 60;
              const magnitude = Math.sqrt(vx * vx + vy * vy);
              if (magnitude > 0) {
                ctx.strokeStyle = '#00ffff';
                ctx.lineWidth = 1.5;
                ctx.beginPath();
                ctx.moveTo(objX, objY);
                ctx.lineTo(objX + vx, objY + vy);
                ctx.stroke();
              }
            }
          }
        } catch (e) {
          console.error('Mini-map error:', e);
          showNotification(`Mini-map error: ${e.message}`);
        }
      }

      // Game loop hook
      const originalDraw = game.beforeDrawAllObjects || game.draw || (() => {});
      game.beforeDrawAllObjects = function() {
        try {
          originalDraw?.apply(this, arguments);
          drawESP();
          drawMiniMap();
        } catch (e) {
          console.error('Game loop error:', e);
          showNotification(`Game loop error: ${e.message}`);
        }
      };

      console.log('Emulation\'s Evo Client Enhanced loaded');
      showNotification('Menu loaded. Press ; to toggle.', 5000);
    } catch (e) {
      console.error('Init mod menu error:', e);
      showNotification(`Menu init error: ${e.message}`);
    }
  }

  waitForGameLoad();
})();