您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Visual-only enhancements for Krunker.io — expanded crosshair, neon/invert, kill flash, and a configurable visible-enemy glow (on-screen only). Saves settings locally.
// ==UserScript== // @name Krunker Fun Pack — Full (Crosshair + Glow + Visuals) // @namespace http://tampermonkey.net/ // @version 1.4 // @description Visual-only enhancements for Krunker.io — expanded crosshair, neon/invert, kill flash, and a configurable visible-enemy glow (on-screen only). Saves settings locally. // @author You // @match *://krunker.io/* // @grant none // ==/UserScript== (function () { 'use strict'; /* --------------------------- Settings storage & defaults --------------------------- */ const STORAGE_KEY = 'krunkerFunPack_v1'; const defaults = { // crosshair crossType: 'Dot', // Dot, Circle, Star, Plus, X, Hybrid, Outlined Dot crossColor: '#00FF00', crossSize: 12, crossThickness: 2, crossGap: 4, crossAlpha: 1.0, // visuals neon: false, invert: false, theme: 'Default', // kill flash killFlash: false, // enemy glow glowEnabled: false, glowColor: '#00FFFF', glowStrength: 1.4, // multiplier for opacity glowSize: 36, // spread radius glowSampleFreq: 150, // ms, how often to run pixel sampling // player name for kill detection (optional) playerName: '' }; let settings = Object.assign({}, defaults, JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}')); function saveSettings() { localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); } /* --------------------------- Helper DOM utilities --------------------------- */ function $tag(name, attrs = {}, html = '') { const el = document.createElement(name); Object.keys(attrs).forEach(k => el.setAttribute(k, attrs[k])); el.innerHTML = html; return el; } /* --------------------------- UI: compact draggable panel --------------------------- */ const panel = $tag('div', { id: 'funpack-panel' }); panel.style.cssText = [ 'position:fixed', 'bottom:12px', 'right:12px', 'width:200px', 'background:rgba(8,8,10,0.9)', 'color:#fff', 'padding:8px', 'font-family:system-ui,Segoe UI,Roboto,Arial,sans-serif', 'font-size:12px', 'border-radius:8px', 'z-index:9999999', 'user-select:none', 'cursor:move' ].join(';'); panel.innerHTML = ` <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px"> <strong>Fun Pack</strong> <small style="opacity:.7">v1.4</small> </div> <div style="margin-bottom:6px"> <label style="display:block;margin-bottom:4px"><small>Crosshair</small></label> <select id="fp_crossType" style="width:100%; margin-bottom:6px"> <option>Dot</option><option>Circle</option><option>Star</option> <option>Plus</option><option>X</option><option>Hybrid</option><option>Outlined Dot</option> </select> <div style="display:flex;gap:6px;align-items:center;margin-bottom:6px"> <input type="color" id="fp_crossColor" style="flex:0 0 40px"> <input type="range" id="fp_crossSize" min="2" max="60" style="flex:1"> </div> <div style="display:flex;gap:6px;align-items:center;margin-bottom:6px"> <input type="range" id="fp_crossThickness" min="1" max="12" style="flex:1"> <input type="range" id="fp_crossGap" min="0" max="30" style="flex:1"> </div> <label style="display:flex;gap:6px;align-items:center"><small>Alpha</small> <input type="range" id="fp_crossAlpha" min="0.1" max="1" step="0.05" style="flex:1"> </label> </div> <div style="margin-bottom:6px;border-top:1px solid rgba(255,255,255,0.04);padding-top:6px"> <label style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px"> <span>Neon Vision</span><input type="checkbox" id="fp_neon"> </label> <label style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px"> <span>Invert Colors</span><input type="checkbox" id="fp_invert"> </label> <label style="display:flex;justify-content:space-between;align-items:center"> <span>Kill Flash</span><input type="checkbox" id="fp_killFlash"> </label> </div> <div style="margin-top:6px;border-top:1px solid rgba(255,255,255,0.04);padding-top:6px"> <label style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px"> <span><small>Visible Enemy Glow</small></span> <input type="checkbox" id="fp_glowEnabled"> </label> <label style="display:flex;gap:6px;align-items:center;margin-bottom:6px"> <input type="color" id="fp_glowColor" style="flex:0 0 44px"> <input type="range" id="fp_glowSize" min="6" max="120" style="flex:1"> </label> <label style="display:flex;gap:6px;align-items:center;margin-bottom:6px"> <span style="font-size:11px">Strength</span> <input type="range" id="fp_glowStrength" min="0.2" max="3" step="0.1" style="flex:1"> </label> <label style="display:flex;gap:6px;align-items:center;"> <span style="font-size:11px">Sample ms</span> <input type="range" id="fp_glowFreq" min="50" max="800" step="25" style="flex:1"> </label> </div> <div style="margin-top:8px;border-top:1px solid rgba(255,255,255,0.04);padding-top:6px;display:flex;gap:6px"> <input id="fp_saveBtn" type="button" value="Save" style="flex:1;padding:6px;background:#2a2a2a;border-radius:6px;border:none;color:#fff;cursor:pointer"> <input id="fp_resetBtn" type="button" value="Reset" style="flex:1;padding:6px;background:#3a3a3a;border-radius:6px;border:none;color:#fff;cursor:pointer"> </div> <div style="margin-top:6px;font-size:11px;opacity:.8">Glow is approximate — only highlights already-visible pixels (no through-wall detection).</div> `; document.body.appendChild(panel); // make draggable (function makeDraggable(el) { let down = false, dx = 0, dy = 0; el.addEventListener('mousedown', (ev) => { down = true; dx = ev.offsetX; dy = ev.offsetY; el.style.transition = 'none'; }); window.addEventListener('mouseup', () => down = false); window.addEventListener('mousemove', (ev) => { if (!down) return; el.style.left = (ev.pageX - dx) + 'px'; el.style.top = (ev.pageY - dy) + 'px'; el.style.right = 'unset'; el.style.bottom = 'unset'; }); })(panel); /* --------------------------- Wire up controls -> settings --------------------------- */ const $ = id => document.getElementById(id); const map = { crossType: 'fp_crossType', crossColor: 'fp_crossColor', crossSize: 'fp_crossSize', crossThickness: 'fp_crossThickness', crossGap: 'fp_crossGap', crossAlpha: 'fp_crossAlpha', neon: 'fp_neon', invert: 'fp_invert', killFlash: 'fp_killFlash', glowEnabled: 'fp_glowEnabled', glowColor: 'fp_glowColor', glowSize: 'fp_glowSize', glowStrength: 'fp_glowStrength', glowSampleFreq: 'fp_glowFreq' }; // populate UI with current settings (or defaults) function initUIFromSettings() { $(map.crossType).value = settings.crossType || defaults.crossType; $(map.crossColor).value = settings.crossColor || defaults.crossColor; $(map.crossSize).value = settings.crossSize || defaults.crossSize; $(map.crossThickness).value = settings.crossThickness || defaults.crossThickness; $(map.crossGap).value = settings.crossGap || defaults.crossGap; $(map.crossAlpha).value = settings.crossAlpha || defaults.crossAlpha; $(map.neon).checked = !!settings.neon; $(map.invert).checked = !!settings.invert; $(map.killFlash).checked = !!settings.killFlash; $(map.glowEnabled).checked = !!settings.glowEnabled; $(map.glowColor).value = settings.glowColor || defaults.glowColor; $(map.glowSize).value = settings.glowSize || defaults.glowSize; $(map.glowStrength).value = settings.glowStrength || defaults.glowStrength; $(map.glowSampleFreq).value = settings.glowSampleFreq || defaults.glowSampleFreq; } initUIFromSettings(); // Save button & reset $('fp_saveBtn').addEventListener('click', () => { saveFromUI(); saveSettings(); alert('Fun Pack settings saved!'); }); $('fp_resetBtn').addEventListener('click', () => { settings = Object.assign({}, defaults); saveSettings(); initUIFromSettings(); applyAll(); }); // On change live update ['fp_crossType','fp_crossColor','fp_crossSize','fp_crossThickness','fp_crossGap','fp_crossAlpha', 'fp_neon','fp_invert','fp_killFlash','fp_glowEnabled','fp_glowColor','fp_glowSize','fp_glowStrength','fp_glowFreq'] .forEach(id => { const el = $(id); if (!el) return; el.addEventListener('input', () => { saveFromUI(); applyAll(); }); el.addEventListener('change', () => { saveFromUI(); applyAll(); }); }); function saveFromUI() { settings.crossType = $(map.crossType).value; settings.crossColor = $(map.crossColor).value; settings.crossSize = parseInt($(map.crossSize).value, 10); settings.crossThickness = parseInt($(map.crossThickness).value, 10); settings.crossGap = parseInt($(map.crossGap).value, 10); settings.crossAlpha = parseFloat($(map.crossAlpha).value); settings.neon = !!$(map.neon).checked; settings.invert = !!$(map.invert).checked; settings.killFlash = !!$(map.killFlash).checked; settings.glowEnabled = !!$(map.glowEnabled).checked; settings.glowColor = $(map.glowColor).value; settings.glowSize = parseInt($(map.glowSize).value, 10); settings.glowStrength = parseFloat($(map.glowStrength).value); settings.glowSampleFreq = parseInt($(map.glowSampleFreq).value, 10); saveSettings(); } /* --------------------------- Crosshair overlay --------------------------- */ const chCanvas = document.createElement('canvas'); chCanvas.width = chCanvas.height = 512; chCanvas.style.position = 'fixed'; chCanvas.style.top = '50%'; chCanvas.style.left = '50%'; chCanvas.style.transform = 'translate(-50%,-50%)'; chCanvas.style.pointerEvents = 'none'; chCanvas.style.zIndex = 9999998; chCanvas.style.mixBlendMode = 'normal'; document.body.appendChild(chCanvas); const chCtx = chCanvas.getContext('2d'); function drawCrosshairOnce() { const scale = window.devicePixelRatio || 1; const size = settings.crossSize || defaults.crossSize; const thick = settings.crossThickness || defaults.crossThickness; const gap = settings.crossGap || defaults.crossGap; const alpha = settings.crossAlpha || defaults.crossAlpha; // canvas size based on size const W = Math.max(120, size * 8); chCanvas.width = W * scale; chCanvas.height = W * scale; chCanvas.style.width = W + 'px'; chCanvas.style.height = W + 'px'; chCtx.setTransform(scale, 0, 0, scale, 0, 0); chCtx.clearRect(0, 0, W, W); chCtx.globalAlpha = alpha; chCtx.strokeStyle = settings.crossColor || '#00FF00'; chCtx.fillStyle = settings.crossColor || '#00FF00'; chCtx.lineWidth = Math.max(1, thick); const cx = W / 2, cy = W / 2; chCtx.beginPath(); switch (settings.crossType) { case 'Dot': chCtx.beginPath(); chCtx.arc(cx, cy, Math.max(1.5, thick+1), 0, Math.PI*2); chCtx.fill(); break; case 'Circle': chCtx.beginPath(); chCtx.arc(cx, cy, size, 0, Math.PI*2); chCtx.stroke(); break; case 'Star': for (let i = 0; i < 5; i++) { const angle = (18 + i * 72) * Math.PI / 180; const x = cx + size * Math.cos(angle); const y = cy - size * Math.sin(angle); chCtx.moveTo(cx, cy); chCtx.lineTo(x, y); } chCtx.stroke(); break; case 'Plus': chCtx.moveTo(cx - size, cy); chCtx.lineTo(cx - gap, cy); chCtx.moveTo(cx + gap, cy); chCtx.lineTo(cx + size, cy); chCtx.moveTo(cx, cy - size); chCtx.lineTo(cx, cy - gap); chCtx.moveTo(cx, cy + gap); chCtx.lineTo(cx, cy + size); chCtx.stroke(); break; case 'X': chCtx.moveTo(cx - size, cy - size); chCtx.lineTo(cx - gap, cy - gap); chCtx.moveTo(cx + gap, cy + gap); chCtx.lineTo(cx + size, cy + size); chCtx.moveTo(cx - size, cy + size); chCtx.lineTo(cx - gap, cy + gap); chCtx.moveTo(cx + gap, cy - gap); chCtx.lineTo(cx + size, cy - size); chCtx.stroke(); break; case 'Hybrid': chCtx.beginPath(); chCtx.arc(cx, cy, size, 0, Math.PI*2); chCtx.stroke(); chCtx.moveTo(cx - size, cy); chCtx.lineTo(cx + size, cy); chCtx.moveTo(cx, cy - size); chCtx.lineTo(cx, cy + size); chCtx.stroke(); break; case 'Outlined Dot': chCtx.beginPath(); chCtx.arc(cx, cy, Math.max(1.5, thick+1), 0, Math.PI*2); chCtx.fill(); chCtx.beginPath(); chCtx.arc(cx, cy, Math.max(4, thick+4), 0, Math.PI*2); chCtx.stroke(); break; default: chCtx.beginPath(); chCtx.arc(cx, cy, Math.max(1.5, thick+1), 0, Math.PI*2); chCtx.fill(); } chCtx.globalAlpha = 1; } /* --------------------------- Visual filters & kill flash --------------------------- */ // kill flash overlay const flashDiv = document.createElement('div'); flashDiv.style.cssText = 'position:fixed;left:0;top:0;width:100%;height:100%;pointer-events:none;opacity:0;z-index:9999997;transition:opacity 0.15s ease'; document.body.appendChild(flashDiv); // kill detection via kill feed (as before) const killFeedObserver = new MutationObserver(muts => { muts.forEach(m => { m.addedNodes.forEach(node => { if (!node || !node.innerText) return; const text = node.innerText || ''; if (!settings.killFlash) return; if (settings.playerName && text.includes(settings.playerName)) { // differentiate headshot patterns const html = node.innerHTML || ''; const isHS = text.includes('HS') || /headshot/i.test(html); triggerKillFlash(isHS ? 'gold' : 'white', isHS ? 700 : 150); } }); }); }); function tryAttachKillFeedObserver() { const feed = document.querySelector('#killFeed') || document.querySelector('.killFeed') || document.querySelector('[id*="kill"]'); if (feed) { killFeedObserver.observe(feed, { childList: true, subtree: true }); return true; } return false; } // attempt attaching periodically if not present yet const killFeedAttachInterval = setInterval(() => { if (tryAttachKillFeedObserver()) clearInterval(killFeedAttachInterval); }, 1000); function triggerKillFlash(color = 'white', ms = 150) { if (!settings.killFlash) return; flashDiv.style.background = color; flashDiv.style.opacity = '1'; setTimeout(() => { flashDiv.style.opacity = '0'; }, ms); } // apply neon/invert via CSS filters (only affects page visuals and overlay) function applyFilters() { const filters = []; if (settings.neon) filters.push('contrast(1.7) saturate(1.6) brightness(1.05)'); if (settings.invert) filters.push('invert(1)'); // apply to body & many run-time elements (game canvas is a canvas, this won't tamper with internal rendering) document.documentElement.style.filter = filters.join(' ') || ''; } /* --------------------------- Visible enemy glow overlay (samples game canvas pixels) --------------------------- */ // overlay canvas for glow drawing const glowCanvas = document.createElement('canvas'); glowCanvas.style.position = 'fixed'; glowCanvas.style.left = '0'; glowCanvas.style.top = '0'; glowCanvas.style.width = '100%'; glowCanvas.style.height = '100%'; glowCanvas.style.pointerEvents = 'none'; glowCanvas.style.zIndex = 9999995; document.body.appendChild(glowCanvas); const glowCtx = glowCanvas.getContext('2d'); // find the largest canvas on page (likely the game's canvas) function findGameCanvas() { const canvases = Array.from(document.querySelectorAll('canvas')); if (!canvases.length) return null; // pick canvas with largest area on screen let best = null, bestArea = 0; canvases.forEach(c => { const rect = c.getBoundingClientRect(); const area = rect.width * rect.height; if (area > bestArea) { bestArea = area; best = c; } }); return best; } let gameCanvas = null; function refreshGameCanvas() { gameCanvas = findGameCanvas(); } refreshGameCanvas(); // try refreshing once per 2s in case game switches canvas setInterval(refreshGameCanvas, 2000); // keep overlay canvas sized correctly function resizeOverlay() { const scale = window.devicePixelRatio || 1; glowCanvas.width = Math.round(window.innerWidth * scale); glowCanvas.height = Math.round(window.innerHeight * scale); glowCanvas.style.width = window.innerWidth + 'px'; glowCanvas.style.height = window.innerHeight + 'px'; glowCtx.setTransform(scale, 0, 0, scale, 0, 0); } window.addEventListener('resize', resizeOverlay); resizeOverlay(); // sampling routine: // cast several short rays from center outwards, sample pixels along them, // consider a hit when we find a pixel whose brightness differs significantly from background. // This is approximate and depends on map/skin colors. function sampleForVisibleEnemy() { if (!gameCanvas || !settings.glowEnabled) { glowCtx.clearRect(0,0,glowCanvas.width, glowCanvas.height); return; } try { // draw the game canvas onto an offscreen canvas so we can read pixels const off = document.createElement('canvas'); const gw = gameCanvas.width, gh = gameCanvas.height; off.width = gw; off.height = gh; const offCtx = off.getContext('2d'); offCtx.drawImage(gameCanvas, 0, 0, gw, gh); // now sample from screen center const rect = gameCanvas.getBoundingClientRect(); const centerX = Math.round(rect.left + rect.width / 2); const centerY = Math.round(rect.top + rect.height / 2); const maxDist = Math.min(rect.width, rect.height) * 0.45; const angles = [0, 15, -15, 30, -30, 45, -45, 90, -90].map(a => a * Math.PI / 180); let hits = []; const threshold = 35; // brightness difference threshold for (let ang of angles) { // sample along ray for (let d = 8; d < maxDist; d += Math.max(6, settings.glowSize / 6)) { const sx = Math.round((centerX + d * Math.cos(ang)) - rect.left); // relative to game canvas const sy = Math.round((centerY - d * Math.sin(ang)) - rect.top); if (sx < 0 || sy < 0 || sx >= gw || sy >= gh) break; // get pixel const p = offCtx.getImageData(sx, sy, 1, 1).data; const r = p[0], g = p[1], b = p[2], a = p[3]; // skip very transparent or nearly black background pixels const brightness = (0.299*r + 0.587*g + 0.114*b); if (a < 40) continue; // Heuristic: player models tend to be colored and not identical to background sky/floor. if (brightness > threshold + 10 || brightness < 220) { // Further heuristics: ensure pixel is not near fully uniform background by checking local neighborhood // sample a small neighborhood const n = offCtx.getImageData(Math.max(0,sx-2), Math.max(0,sy-2), Math.min(5, gw-sx+2), Math.min(5, gh-sy+2)).data; // compute local variance let mean = 0, count = n.length/4; for (let i=0;i<n.length;i+=4) { mean += (0.299*n[i] + 0.587*n[i+1] + 0.114*n[i+2]); } mean /= count; let variance = 0; for (let i=0;i<n.length;i+=4) { const val = (0.299*n[i] + 0.587*n[i+1] + 0.114*n[i+2]); variance += (val - mean) * (val - mean); } variance /= Math.max(1, count); if (variance > 30) { // map to screen coordinates const screenX = rect.left + sx; const screenY = rect.top + sy; hits.push({x: screenX, y: screenY}); break; // stop along this ray } } } } // draw glow overlay for hits (convert to overlay coordinates) glowCtx.clearRect(0,0,glowCanvas.width, glowCanvas.height); if (hits.length) { glowCtx.save(); glowCtx.globalCompositeOperation = 'lighter'; for (let h of hits) { const gx = h.x, gy = h.y; const radius = settings.glowSize || 36; const color = settings.glowColor || '#00FFFF'; const strength = settings.glowStrength || 1.4; // convert to CSS pixels for drawing glowCtx.beginPath(); glowCtx.fillStyle = hexToRgba(color, 0.06 * strength); glowCtx.arc(gx, gy, radius * 0.9, 0, Math.PI*2); glowCtx.fill(); glowCtx.beginPath(); glowCtx.fillStyle = hexToRgba(color, 0.12 * strength); glowCtx.arc(gx, gy, radius * 0.6, 0, Math.PI*2); glowCtx.fill(); glowCtx.beginPath(); glowCtx.fillStyle = hexToRgba(color, 0.22 * strength); glowCtx.arc(gx, gy, radius * 0.3, 0, Math.PI*2); glowCtx.fill(); } glowCtx.restore(); } else { // nothing visible glowCtx.clearRect(0,0,glowCanvas.width, glowCanvas.height); } } catch (err) { // fail silently — sampling can throw if cross-origin or timing issues // clear overlay glowCtx.clearRect(0,0,glowCanvas.width, glowCanvas.height); // console.debug('Glow sample error', err); } } function hexToRgba(hex, alpha=1) { // supports #RRGGBB const h = hex.replace('#',''); const r = parseInt(h.substring(0,2),16); const g = parseInt(h.substring(2,4),16); const b = parseInt(h.substring(4,6),16); return `rgba(${r},${g},${b},${alpha})`; } // schedule sampling loop (throttled by settings.glowSampleFreq) let samplingTimer = null; function startSamplingLoop() { if (samplingTimer) clearInterval(samplingTimer); if (!settings.glowEnabled) return; samplingTimer = setInterval(sampleForVisibleEnemy, Math.max(50, settings.glowSampleFreq || 150)); } function stopSamplingLoop() { if (samplingTimer) { clearInterval(samplingTimer); samplingTimer = null; } } /* --------------------------- Apply everything --------------------------- */ function applyAll() { drawCrosshairOnce(); applyFilters(); if (settings.glowEnabled) startSamplingLoop(); else stopSamplingLoop(); } // initial apply applyAll(); // observe for changes to primary canvas (re-attach if needed) const bodyObserver = new MutationObserver(() => { refreshGameCanvas(); }); bodyObserver.observe(document.body, { childList: true, subtree: true }); /* --------------------------- Utility: keep overlay sizing synced --------------------------- */ function syncOverlaySize() { // update glowCanvas size (device pixel ratio handled inside sampling) const scale = window.devicePixelRatio || 1; glowCanvas.width = Math.round(window.innerWidth * scale); glowCanvas.height = Math.round(window.innerHeight * scale); glowCanvas.style.width = window.innerWidth + 'px'; glowCanvas.style.height = window.innerHeight + 'px'; glowCtx.setTransform(scale, 0, 0, scale, 0, 0); } window.addEventListener('resize', syncOverlaySize); syncOverlaySize(); /* --------------------------- React to UI load+changes --------------------------- */ // On load, ensure UI reflects settings initUIFromSettings(); applyAll(); // minor: refresh crosshair on animation frames for crispness (only when settings change) let lastCrossHash = ''; setInterval(() => { const hash = [settings.crossType, settings.crossSize, settings.crossThickness, settings.crossColor, settings.crossGap, settings.crossAlpha].join('|'); if (hash !== lastCrossHash) { lastCrossHash = hash; drawCrosshairOnce(); } }, 300); // When page unload, stop timers window.addEventListener('beforeunload', () => { stopSamplingLoop(); }); // Export apply function globally to help debugging via console if needed window.__krunkerFunPackApply = applyAll; })();