您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Really advanced tracers for evoworld.io
// ==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">×</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(); })();