您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Ultimate Slither.io Mod Menu with Chat & Custom UI - Fixed chat toggle and simplify
当前为
// ==UserScript== // @name SLITHER.IO MOD MENU W/ CHAT - DSC.GG/143X VXII // @namespace http://tampermonkey.net/ // @version X-chat-keybinds // @description Ultimate Slither.io Mod Menu with Chat & Custom UI - Fixed chat toggle and simplify // @author GITHUB.COM/DXXTHLY - HTTPS://DSC.GG/143X by: dxxthly. & waynesg on Discord // @icon https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQUNcRl2Rh40pZLhgffYGFDRLbYJ4qfMNwddQ&s.png // @match http://slither.io/ // @match https://slither.io/ // @match http://slither.com/io // @match https://slither.com/io // @grant none // ==/UserScript== (function () { 'use strict'; // --- Custom BG Patch (MUST be first!) --- window.__customBgUrlCurrent = 'https://slither.io/s2/bg54.jpg'; // Default if (!window.__customBgPatched) { window.__customBgPatched = true; const originalDrawImage = CanvasRenderingContext2D.prototype.drawImage; CanvasRenderingContext2D.prototype.drawImage = function(img, ...args) { if ( img && img.src && img.src.includes('bg54.jpg') && window.__customBgUrlCurrent ) { const customImg = new window.Image(); customImg.crossOrigin = "anonymous"; customImg.src = window.__customBgUrlCurrent; return originalDrawImage.call(this, customImg, ...args); } return originalDrawImage.apply(this, arguments); }; } // === CONFIG === const config = { menuPosition: 'right', defaultCircleRadius: 150, circleRadiusStep: 20, minCircleRadius: 50, maxCircleRadius: 300, deathSoundURL: 'https://audio.jukehost.co.uk/WwASzZ0a1wJDKubIcoZzin8J7kycCt5l.mp3', godModeVideoURL: 'https://youtu.be/ghAap5IWu1Y', defaultMenuName: 'DSC.GG/143X', defaultMenuColor: '#4CAF50', chatMaxMessages: 50, chatMaxMessageLength: 100, chatProfanityFilter: true, chatProfanityList: ['fuck', 'shit', 'asshole', 'bitch', 'cunt', 'nigger', 'fag', 'retard'] }; // === STATE === const state = { keybinds: JSON.parse(localStorage.getItem('modKeybinds')) || { toggleMenu: 'm', toggleKeybinds: '-', circleRestriction: 'k', circleSmaller: 'j', circleLarger: 'l', autoCircle: 'a', autoBoost: 'b', fpsDisplay: 'f', autoRespawn: 's', deathSound: 'v', showServer: 't', chatEnabled: '/', zoomIn: 'z', zoomOut: 'x', zoomReset: 'c', screenshot: 'p', github: 'g', discord: 'd', godMode: 'y', reddit: 'r' }, features: { circleRestriction: false, autoCircle: false, performanceMode: 1, deathSound: true, snakeTrail: false, snakeTrailColor: '#FFD700', fpsDisplay: false, autoBoost: false, showServer: false, autoRespawn: false, chatVisible: true, chatEnabled: true, chatProfanityFilter: config.chatProfanityFilter, chatFocus: false, keybindsEnabled: true }, menuVisible: true, zoomFactor: 1.0, circleRadius: config.defaultCircleRadius, fps: 0, fpsFrames: 0, fpsLastCheck: Date.now(), deathSound: new Audio(config.deathSoundURL), isInGame: false, boosting: false, autoCircleAngle: 0, ping: 0, server: '', lastSnakeAlive: true, boostingInterval: null, menuName: localStorage.getItem('modMenuName') || config.defaultMenuName, menuColor: localStorage.getItem('modMenuColor') || config.defaultMenuColor, showCustomization: sessionStorage.getItem('showCustomization') === 'false' ? false : true, simplified: sessionStorage.getItem('modMenuSimplified') === 'true', chatMessages: [], uiLayout: JSON.parse(localStorage.getItem('modMenuUILayout')) || { menu: { x: null, y: null, width: null, height: null }, chat: { x: 20, y: 100, width: 300, height: 200 }, minimap: { x: null, y: null, width: null, height: null } }, draggingElement: null, resizingElement: null, dragStartX: 0, dragStartY: 0, elementStartX: 0, elementStartY: 0, elementStartWidth: 0, elementStartHeight: 0 }; // Ensure all default keybinds are present in state.keybinds const defaultKeybinds = { toggleMenu: 'm', toggleKeybinds: '-', circleRestriction: 'k', circleSmaller: 'j', circleLarger: 'l', autoCircle: 'a', autoBoost: 'b', fpsDisplay: 'f', autoRespawn: 's', deathSound: 'v', showServer: 't', chatEnabled: 'enter', zoomIn: 'z', zoomOut: 'x', zoomReset: 'c', screenshot: 'p', github: 'g', discord: 'd', godMode: 'y', reddit: 'r' }; Object.entries(defaultKeybinds).forEach(([action, key]) => { if (!state.keybinds.hasOwnProperty(action)) { state.keybinds[action] = key; } }); function loadSavedServers() { try { return JSON.parse(localStorage.getItem('customServerList') || '[]'); } catch { return []; } } function saveServers(list) { localStorage.setItem('customServerList', JSON.stringify(list)); } function updateServerDropdown() { const selectSrv = document.getElementById('select-srv'); if (!selectSrv) return; selectSrv.innerHTML = '<option value="">Select a Server</option>'; const servers = loadSavedServers(); servers.forEach((ip, i) => { const opt = document.createElement('option'); opt.value = ip; opt.text = `${i+1}. ${ip}`; selectSrv.appendChild(opt); }); } // update server ip loop wayne function updateServerIpLoop() { let ip = null, port = null; // Try to get the server IP and port from the game if (window.bso && window.bso.ip && window.bso.po) { ip = window.bso.ip; port = window.bso.po; } if (ip && port) { state.server = `${ip}:${port}`; } else { state.server = ''; } setTimeout(updateServerIpLoop, 1000); // Check every second } updateServerIpLoop(); // === VIP MEMBERS // DISCORD === const vipMembers = [ { uid: "crcOY9hoRrfayStCxMVm7Zdx2W92", name: "stevao" }, // first discord vip { uid: "DhGhICAZwkRa7wuMsyquM9a5uO92", name: "LUANBLAYNER" }, { uid: "VIP_UID_2", name: "AnotherVIP" } ]; function isVip(uid, name) { return vipMembers.some(vip => vip.uid === uid && vip.name.toLowerCase() === (name || '').toLowerCase() ); } function vipGlowStyle(name, color) { return `<span style=" color:#fff; font-weight:bold; text-shadow:0 0 8px ${color},0 0 16px ${color},0 0 24px ${color}; ">${name}</span>`; } // The thingy for the chat to be in order let chatMessagesArray = []; // Server list let forcedServer = null; // { ip: "IP", port: "PORT" } // state variables let chatHistory = []; let autoCircleRAF = null; // ===== AUTO RESPAWN SYSTEM ===== let autoRespawnDead = false; let autoRespawnSpam = null; let deathCheckInterval = null; // dxxthly afk let afkOn = false; let afkInterval = null; // track real mouse cursor position let realMouseX = window.innerWidth / 2; let realMouseY = window.innerHeight / 2; document.addEventListener('mousemove', function(e) { realMouseX = e.clientX; realMouseY = e.clientY; }); function syncServerBoxWithMenu() { const box = document.getElementById('custom-server-box'); const nameSpan = document.getElementById('custom-server-box-name'); const serverListBtn = document.getElementById('server-list-btn'); if (!box || !nameSpan) return; nameSpan.textContent = state.menuName; nameSpan.style.color = state.menuColor; nameSpan.style.textShadow = `0 0 8px ${state.menuColor}, 0 0 16px ${state.menuColor}`; box.style.borderColor = state.menuColor; if (serverListBtn) { serverListBtn.style.background = state.menuColor; serverListBtn.style.boxShadow = `0 0 8px ${state.menuColor}66`; } } const zoomSteps = [ 0.1, 0.125, 0.15, 0.175, 0.2, 0.225, 0.25, 0.275, 0.3, 0.325, 0.35, 0.375, 0.4, 0.425, 0.45, 0.475, 0.5, 0.525, 0.55, 0.575, 0.6, 0.625, 0.65, 0.675, 0.7, 0.725, 0.75, 0.775, 0.8, 0.825, 0.85, 0.875, 0.9, 0.925, 0.95, 0.975, 1.0, 1.025, 1.05, 1.075, 1.1, 1.125, 1.15, 1.175, 1.2, 1.225, 1.25, 1.275, 1.3, 1.325, 1.35, 1.375, 1.4, 1.425, 1.45, 1.475, 1.5, 1.525, 1.55, 1.575, 1.6, 1.625, 1.65, 1.675, 1.7, 1.725, 1.75, 1.775, 1.8, 1.825, 1.85, 1.875, 1.9, 1.925, 1.95, 1.975, 2.0, 2.25, 2.5, 2.75, 3.0, 3.25, 3.5, 3.75, 4.0, 4.25, 4.5, 4.75, 5.0, 5.25, 5.5, 5.75, 6.0, 6.25, 6.5, 6.75, 7.0, 7.25, 7.5, 7.75, 8.0, 8.25, 8.5, 8.75, 9.0, 9.25, 9.5, 9.75, 10.0 ]; function addServerBox() { const check = setInterval(() => { const login = document.getElementById('login'); const nickInput = document.getElementById('nick'); if (login && nickInput) { clearInterval(check); // Prevent duplicate if (document.getElementById('custom-server-box')) return; // Create the box DEN DEN DEN const box = document.createElement('div'); box.id = 'custom-server-box'; box.style.cssText = ` margin: 24px auto 0 auto; max-width: 340px; background: rgba(30,30,30,0.96); border: 2px solid #6bff66; border-radius: 18px; box-shadow: 0 0 12px #00ff0044; padding: 18px 18px 10px 18px; text-align: center; font-family: 'ink free', Arial, sans-serif; font-size: 1.1em; color: #fff; letter-spacing: 1px; font-weight: bold; position: relative; `; box.innerHTML = ` <div style="margin-bottom:8px;"> <span id="custom-server-box-name" style=" color:${state.menuColor}; font-size:1.5em; font-family:'ink free',Arial,sans-serif; text-shadow:0 0 8px ${state.menuColor},0 0 16px ${state.menuColor}; font-weight:bold; letter-spacing:1px; transition:color 0.2s, text-shadow 0.2s; "> ${state.menuName} </span> </div> <div style="display:flex; justify-content:center; align-items:center; gap:10px; margin-bottom:10px;"> <input id="server-ip" type="text" placeholder="Server address (IP:port)" style="flex:2; min-width:0; padding:8px 10px; background:rgba(80,80,80,0.4); color:#e0e0ff; border:none; border-radius:8px; outline:none; font-size:1em;"> <input id="connect-btn" type="button" value="Play" style="flex:1; min-width:60px; height:36px; border-radius:12px; color:#FFF; background: ${state.menuColor}; border:none; outline:none; cursor:pointer; font-weight:bold;"> </div> <div style="display:flex; justify-content:center; align-items:center; gap:10px; margin-bottom:10px;"> <input id="save-ip-btn" type="button" value="Save" style="flex:1; min-width:60px; height:36px; border-radius:12px; color:#FFF; background: ${state.menuColor}; border:none; outline:none; cursor:pointer; font-weight:bold;"> </div> <select id="select-srv" style="display:block; margin:10px auto 6px auto; width:90%; background:rgba(145,145,145,0.7); border:none; border-radius:12px; padding:6px 10px; font-size:1em; text-align:center;"> <option value="">Select a Server</option> </select> <a id="server-list-btn" href="https://ntl-slither.com/ss/?reg=na" target="_blank" style=" display: block; margin: 14px auto 0 auto; width: 95%; background: ${state.menuColor}; color: #fff; border: none; border-radius: 12px; padding: 12px 0; font-size: 1.15em; font-family: inherit; font-weight: bold; letter-spacing: 1px; cursor: pointer; box-shadow: 0 0 8px ${state.menuColor}66; text-align: center; text-decoration: none; transition: background 0.2s; " > Server List </a> `; box.style.borderColor = state.menuColor; // Insert the box into the DOM FIRST: let parent = nickInput.parentElement; if (parent && parent.nextSibling) { parent.parentNode.insertBefore(box, parent.nextSibling.nextSibling); } else { login.appendChild(box); } // call this pita updateServerDropdown(); // Dropdown selection fills the input const selectSrv = document.getElementById('select-srv'); selectSrv.onchange = function() { document.getElementById('server-ip').value = this.value; }; // Save button handler document.getElementById('save-ip-btn').onclick = function() { const ipInput = document.getElementById('server-ip'); if (!ipInput || !ipInput.value.trim()) return; const ip = ipInput.value.trim(); if (!ip.includes(':') || ip.split(':')[0].trim() === '' || ip.split(':')[1].trim() === '') { alert("Please enter a valid IP:Port (e.g., 15.204.212.200:444 or server.domain.com:444)"); return; } let servers = loadSavedServers(); // Check for duplicates (case insensitive, ignoring whitespace) const normalized = ip.toLowerCase().replace(/\s+/g, ''); const isDuplicate = servers.some(s => s.toLowerCase().replace(/\s+/g, '') === normalized); if (!isDuplicate) { servers.push(ip); saveServers(servers); updateServerDropdown(); if (selectSrv) selectSrv.value = ip; } else { alert("This server is already in your list!"); } }; document.getElementById('connect-btn').onclick = function() { const ipInput = document.getElementById('server-ip'); if (!ipInput || !ipInput.value.trim()) return; const ip = ipInput.value.trim(); const parts = ip.split(':'); const ipPart = parts[0]; const portPart = parts[1] || "444"; // Save for persistence and Play Again forcedServer = { ip: ipPart, port: portPart }; localStorage.setItem('forcedServer', JSON.stringify(forcedServer)); // Use forceServer if available (most reliable, like your friend's mod) if (typeof window.forceServer === "function") { window.forceServer(ipPart, portPart); } // Set Slither.io variables (extra safety) window.forcing = true; if (!window.bso) window.bso = {}; window.bso.ip = ipPart; window.bso.po = portPart; // Always call connect() if (typeof window.connect === "function") { window.connect(); } // Simulate Play button click to trigger game start if needed const playBtn = document.getElementById('playh') || document.querySelector('.btn.btn-primary.btn-play-guest'); if (playBtn) playBtn.click(); // Optionally start checking connection status cause if she dont like u she dont like u if (typeof connectionStatus === "function") setTimeout(connectionStatus, 1000); }; } }, 100); } addServerBox(); let retry = 0; function connectionStatus() { if (!window.connecting || retry == 10) { window.forcing = false; retry = 0; return; } retry++; setTimeout(connectionStatus, 1000); } // Death detection function autoRespawnCheck() { if (!state.features.autoRespawn) { autoRespawnDead = false; stopAutoRespawnSpam(); return; } // Enhanced death detection const isDead = ( (window.snake && !window.snake.alive) || (window.dead_mtm !== undefined && window.dead_mtm !== -1) || (document.getElementById('died')?.style.display !== 'none') || (document.querySelector('.playagain')?.offsetParent !== null) ); if (isDead && !autoRespawnDead) { autoRespawnDead = true; startAutoRespawnSpam(); } else if (!isDead && autoRespawnDead) { autoRespawnDead = false; stopAutoRespawnSpam(); } } // Spam logic function startAutoRespawnSpam() { if (autoRespawnSpam) return; attemptAutoRespawn(); // Immediate attempt autoRespawnSpam = setInterval(attemptAutoRespawn, 50); } function attemptAutoRespawn() { if (!autoRespawnDead || !state.features.autoRespawn) { stopAutoRespawnSpam(); return; } // Handle nickname input const nickInput = document.getElementById('nick'); if (nickInput && !nickInput.value.trim()) { nickInput.value = localStorage.getItem("nickname") || "Anon"; nickInput.dispatchEvent(new Event('input', { bubbles: true })); } if (nickInput) nickInput.focus(); // Send Enter key document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true })); } function stopAutoRespawnSpam() { if (autoRespawnSpam) { clearInterval(autoRespawnSpam); autoRespawnSpam = null; } } // Toggle functions function enableAutoRespawn() { if (!deathCheckInterval) { deathCheckInterval = setInterval(autoRespawnCheck, 100); } } function disableAutoRespawn() { if (deathCheckInterval) { clearInterval(deathCheckInterval); deathCheckInterval = null; } autoRespawnDead = false; stopAutoRespawnSpam(); } // Initialize if enabled if (state.features.autoRespawn) enableAutoRespawn(); // Prime audio on ANY user interaction const primeAudio = () => { state.deathSound.volume = 0.01; state.deathSound.play().then(() => { state.deathSound.pause(); state.deathSound.currentTime = 0; state.deathSound.volume = 1; }).catch(console.error); document.removeEventListener('click', primeAudio); document.removeEventListener('keydown', primeAudio); }; document.addEventListener('click', primeAudio); document.addEventListener('keydown', primeAudio); // popup css fadein ish modern lookish menu? const style = document.createElement('style'); style.textContent = ` .profile-popup { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(1); min-width: 270px; max-width: 90vw; background: var(--menu-color, #4CAF50); color: #fff; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.25), 0 2px 8px rgba(0,0,0,0.14); padding: 24px 32px 18px 32px; z-index: 99999; font-family: inherit; font-size: 1.08em; animation: fadeInProfile 0.21s cubic-bezier(.42,0,1,1); display: flex; flex-direction: column; align-items: center; } @keyframes fadeInProfile { from { opacity: 0; transform: translate(-50%, -60%) scale(0.95);} to { opacity: 1; transform: translate(-50%, -50%) scale(1);} } .profile-popup .close-btn { position: absolute; top: 10px; right: 16px; background: none; border: none; color: #fff; font-size: 1.5em; cursor: pointer; opacity: 0.7; transition: opacity 0.15s; } .profile-popup .close-btn:hover { opacity: 1; } .profile-popup .avatar { width: 64px; height: 64px; border-radius: 50%; background: #222; margin-bottom: 14px; object-fit: cover; border: 2px solid #fff; } .profile-popup .status-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; vertical-align: middle; } `; document.head.appendChild(style); // === Helper: Hex to RGBA === function hexToRgba(hex, alpha = 1) { let c = hex.replace('#', ''); if (c.length === 3) c = c[0]+c[0]+c[1]+c[1]+c[2]+c[2]; const num = parseInt(c, 16); return `rgba(${(num>>16)&255},${(num>>8)&255},${num&255},${alpha})`; } let lastChatMessageTime = 0; const chatCooldown = 7000; // Editing time will disable your messages from being sent to others // === Profanity Filter === function filterProfanity(text) { if (!state.features.chatProfanityFilter) return text; return text.split(/\b/).map(word => { const lowerWord = word.toLowerCase(); if (config.chatProfanityList.some(profanity => lowerWord.includes(profanity))) { return '*'.repeat(word.length); } return word; }).join(''); } function replaceLinksWithDiscord(text) { const urlRegex = /https?:\/\/[^\s]+|www\.[^\s]+/gi; return text.replace(urlRegex, 'https://dsc.gg/143X'); } document.addEventListener('pointerdown', function primeDeathSound() { state.deathSound.volume = 1; state.deathSound.play().catch(()=>{}); state.deathSound.pause(); state.deathSound.currentTime = 0; document.removeEventListener('pointerdown', primeDeathSound); }); function primeDeathSound() { state.deathSound.volume = 0; state.deathSound.play().catch(()=>{}); state.deathSound.pause(); state.deathSound.currentTime = 0; state.deathSound.volume = 1; document.removeEventListener('pointerdown', primeDeathSound); document.removeEventListener('keydown', primeDeathSound); } document.addEventListener('pointerdown', primeDeathSound); document.addEventListener('keydown', primeDeathSound); // === CHAT SYSTEM === function createChatSystem() { const chatContainer = document.createElement('div'); chatContainer.id = 'mod-menu-chat-container'; chatContainer.style.position = 'fixed'; chatContainer.style.left = `${state.uiLayout.chat.x}px`; chatContainer.style.top = `${state.uiLayout.chat.y}px`; chatContainer.style.width = `${state.uiLayout.chat.width}px`; chatContainer.style.height = `${state.uiLayout.chat.height}px`; chatContainer.style.zIndex = '9999'; chatContainer.style.display = state.features.chatVisible ? 'flex' : 'none'; chatContainer.style.flexDirection = 'column'; chatContainer.style.overflow = 'hidden'; chatContainer.style.userSelect = 'none'; // Chat tabs const chatTabs = document.createElement('div'); chatTabs.style.display = 'flex'; chatTabs.style.borderBottom = `1px solid ${hexToRgba(state.menuColor, 0.3)}`; const chatTab = document.createElement('div'); chatTab.textContent = '143X Chat'; chatTab.style.flex = '1'; chatTab.style.padding = '8px'; chatTab.style.textAlign = 'center'; chatTab.style.cursor = 'pointer'; chatTab.style.background = hexToRgba(state.menuColor, 0.2); chatTab.style.fontWeight = 'bold'; chatTab.style.color = '#fff'; chatTab.onclick = () => { document.getElementById('mod-menu-chat-body').style.display = 'flex'; document.getElementById('mod-menu-online-users').style.display = 'none'; chatTab.style.background = hexToRgba(state.menuColor, 0.2); usersTab.style.background = 'transparent'; }; const usersTab = document.createElement('div'); usersTab.textContent = 'Online Users'; usersTab.style.flex = '1'; usersTab.style.padding = '8px'; usersTab.style.textAlign = 'center'; usersTab.style.cursor = 'pointer'; usersTab.style.background = 'transparent'; usersTab.style.color = '#fff'; usersTab.onclick = () => { document.getElementById('mod-menu-chat-body').style.display = 'none'; document.getElementById('mod-menu-online-users').style.display = 'flex'; chatTab.style.background = 'transparent'; usersTab.style.background = hexToRgba(state.menuColor, 0.2); // No manual updateOnlineUsers() call! }; chatTabs.appendChild(chatTab); chatTabs.appendChild(usersTab); chatContainer.appendChild(chatTabs); // Chat header const chatHeader = document.createElement('div'); chatHeader.style.padding = '8px 12px'; chatHeader.style.background = hexToRgba(state.menuColor, 0.2); chatHeader.style.display = 'flex'; chatHeader.style.justifyContent = 'space-between'; chatHeader.style.alignItems = 'center'; chatHeader.style.cursor = 'move'; chatHeader.dataset.draggable = 'true'; const chatToggle = document.createElement('div'); chatToggle.textContent = '✖'; // Unicode X chatToggle.style.cursor = 'pointer'; chatToggle.style.fontSize = '18px'; chatToggle.style.padding = '0 5px'; chatToggle.title = state.features.chatVisible ? 'Hide chat' : 'Show chat'; chatToggle.onclick = () => { toggleChatVisible(); // or toggleChatDisplay() }; chatHeader.appendChild(chatToggle); chatContainer.appendChild(chatHeader); // Main chat area const chatArea = document.createElement('div'); chatArea.style.flex = '1'; chatArea.style.display = 'flex'; chatArea.style.flexDirection = 'column'; chatArea.style.overflow = 'hidden'; chatArea.style.background = 'rgba(17, 17, 17, 0.85)'; chatArea.style.borderRadius = '0 0 10px 10px'; chatArea.style.border = `2px solid ${state.menuColor}`; chatArea.style.borderTop = 'none'; // Chat messages const chatBody = document.createElement('div'); chatBody.id = 'mod-menu-chat-body'; chatBody.style.flex = '1'; chatBody.style.padding = '8px 12px'; chatBody.style.overflowY = 'auto'; chatBody.style.display = 'flex'; chatBody.style.flexDirection = 'column'; chatArea.appendChild(chatBody); // Online users list const onlineUsers = document.createElement('div'); onlineUsers.id = 'mod-menu-online-users'; onlineUsers.style.flex = '1'; onlineUsers.style.padding = '8px 12px'; onlineUsers.style.overflowY = 'auto'; onlineUsers.style.display = 'none'; onlineUsers.style.flexDirection = 'column'; onlineUsers.innerHTML = '<div style="text-align:center;color:#aaa;">Loading users...</div>'; chatArea.appendChild(onlineUsers); // Chat input const chatInput = document.createElement('input'); chatInput.id = 'mod-menu-chat-input'; chatInput.type = 'text'; chatInput.placeholder = 'Press "/" to start typing...'; chatInput.style.width = '100%'; chatInput.style.padding = '8px 12px'; chatInput.style.border = 'none'; chatInput.style.borderTop = `1px solid ${hexToRgba(state.menuColor, 0.3)}`; chatInput.style.background = 'rgba(255,255,255,0.1)'; chatInput.style.color = '#fff'; chatInput.style.outline = 'none'; chatInput.style.display = 'block'; chatArea.appendChild(chatInput); chatContainer.appendChild(chatArea); // Resize handle const resizeHandle = document.createElement('div'); resizeHandle.style.position = 'absolute'; resizeHandle.style.right = '0'; resizeHandle.style.bottom = '0'; resizeHandle.style.width = '15px'; resizeHandle.style.height = '15px'; resizeHandle.style.cursor = 'nwse-resize'; resizeHandle.style.backgroundColor = hexToRgba(state.menuColor, 0.5); resizeHandle.style.display = 'block'; resizeHandle.dataset.resizable = 'true'; chatContainer.appendChild(resizeHandle); document.body.appendChild(chatContainer); // Make draggable and resizable makeDraggable(chatContainer, chatHeader); makeResizable(chatContainer, resizeHandle); } // sync chatbox color function syncChatBoxWithMenu() { const chatContainer = document.getElementById('mod-menu-chat-container'); if (!chatContainer) return; // Tabs const chatTabs = chatContainer.querySelector('div'); if (chatTabs) chatTabs.style.borderBottom = `1px solid ${hexToRgba(state.menuColor, 0.3)}`; // Tab backgrounds const chatTab = chatTabs?.children?.[0]; const usersTab = chatTabs?.children?.[1]; if (chatTab && chatTab.style.background) chatTab.style.background = hexToRgba(state.menuColor, 0.2); if (usersTab && usersTab.style.background) usersTab.style.background = 'transparent'; // active tab will update on click // Header const chatHeader = chatContainer.querySelector('div:nth-child(2)'); if (chatHeader) chatHeader.style.background = hexToRgba(state.menuColor, 0.2); // Chat area border const chatArea = chatContainer.querySelector('div:nth-child(3)'); if (chatArea) { chatArea.style.border = `2px solid ${state.menuColor}`; chatArea.style.borderTop = 'none'; } // Resize handle const resizeHandle = chatContainer.querySelector('div[style*="cursor: nwse-resize"]'); if (resizeHandle) resizeHandle.style.backgroundColor = hexToRgba(state.menuColor, 0.5); // Chat input const chatInput = document.getElementById('mod-menu-chat-input'); if (chatInput) chatInput.style.borderTop = `1px solid ${hexToRgba(state.menuColor, 0.3)}`; } // Place these helpers OUTSIDE (above) your loadFirebaseChat function: function filterProfanity(text) { const profanityList = [ 'fuck', 'shit', 'asshole', 'bitch', 'cunt', 'nigg3r', 'faggot', 'nigger', 'fag', 'retard', 'whore', 'slut', 'dick', 'douche', 'prick', 'pussy', 'cock', 'bollocks', 'arsehole', 'twat', 'jerkoff', 'motherfucker', 'dumbass', 'dumbfuck', 'crap', 'bollock', 'bugger', 'git', 'wanker', 'arse', 'clit', 'cum', 'blowjob', 'handjob', 'shitface', 'dickhead', 'tosser', 'knob', 'knobhead', 'pillock', 'tosspot', 'twatface', 'cumshot', 'fucked', 'fucking', 'shite', 'bastard', 'slag', 'minger', 'gash', 'bint', 'minge', 'prick', 'shithead', 'wank', 'shitbag' ]; return text.split(/\b/).map(word => { const lowerWord = word.toLowerCase(); if (profanityList.some(profanity => lowerWord.includes(profanity))) { return '*'.repeat(word.length); } return word; }).join(''); } function replaceLinksWithDiscord(text) { const urlRegex = /https?:\/\/[^\s]+|www\.[^\s]+/gi; return text.replace(urlRegex, 'https://dsc.gg/143X'); } function rainbowTextStyle(name) { const rainbowColors = ["#ef3550","#f48fb1","#7e57c2","#2196f3","#26c6da","#43a047","#eeff41","#f9a825","#ff5722"]; return name.split('').map((char, i) => `<span style="color:${rainbowColors[i % rainbowColors.length]}">${char}</span>` ).join(''); } function loadFirebaseChat() { // Load Firebase scripts const script1 = document.createElement('script'); script1.src = 'https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js'; script1.onload = () => { const script2 = document.createElement('script'); script2.src = 'https://www.gstatic.com/firebasejs/8.10.0/firebase-database.js'; script2.onload = () => { const script3 = document.createElement('script'); script3.src = 'https://www.gstatic.com/firebasejs/8.10.0/firebase-auth.js'; script3.onload = () => { const firebaseConfig = { apiKey: "AIzaSyCtTloqGNdhmI3Xt0ta11vF0MQJHiKpO7Q", authDomain: "chatforslither.firebaseapp.com", databaseURL: "https://chatforslither-default-rtdb.firebaseio.com", projectId: "chatforslither", storageBucket: "chatforslither.appspot.com", messagingSenderId: "1045559625491", appId: "1:1045559625491:web:79eb8200eb87edac00bce6" }; if (!firebase.apps.length) firebase.initializeApp(firebaseConfig); const auth = firebase.auth(); auth.signInAnonymously().then(async (userCredential) => { const uid = userCredential.user.uid; const nickname = localStorage.getItem("nickname") || "Anon"; const userRef = firebase.database().ref("onlineUsers/" + uid); userRef.onDisconnect().remove(); userRef.set({ name: nickname, uid: uid, lastActive: Date.now(), chatNameColor: localStorage.getItem("chatNameColor") || "#FFD700" }); setInterval(() => { userRef.update({ lastActive: Date.now() }); }, 30000); // every 30 seconds // === ONLINE USERS LISTENER === firebase.database().ref("onlineUsers").on("value", snapshot => { const users = snapshot.val() || {}; const onlineUsersEl = document.getElementById('mod-menu-online-users'); if (onlineUsersEl) { const now = Date.now(); const usersList = Object.entries(users) // Use entries not values .filter(([uid, user]) => now - user.lastActive < 300000) .map(([uid, user]) => ` <div style="margin-bottom:5px;..."> <span style="color:${user.chatNameColor};font-weight:bold;"> ${user.name} ${uid === auth.currentUser.uid ? '(You)' : ''} </span> <span style="color:#fff !important;">Online</span> </div> `).join(''); onlineUsersEl.innerHTML = usersList || '<div ...>No users online</div>'; } }); // Helper to get role async function getRole(targetUid) { const snap = await firebase.database().ref(`roles/${targetUid}`).once('value'); return snap.val(); } // Helper to check mute/timeout async function getSanction(type, targetUid) { const snap = await firebase.database().ref(`${type}/${targetUid}`).once('value'); return snap.exists() ? snap.val() : null; } // Helper to send system message function sendSystemMessage(text) { firebase.database().ref("slitherChat").push({ uid: "system", name: "System", text: `<span style="color:red">${text}</span>`, time: Date.now(), chatNameColor: "#FF4444" }); } // Moderation buttons function createModButtons(targetUid, yourRole) { const div = document.createElement('div'); div.style.display = 'inline-block'; let html = ''; if (yourRole === 'owner') { html += `<button class="mod-btn ban" data-uid="${targetUid}">Ban</button>`; } if (['owner','admin'].includes(yourRole)) { html += `<button class="mod-btn timeout" data-uid="${targetUid}">Timeout</button>`; } if (['owner','admin','mod'].includes(yourRole)) { html += `<button class="mod-btn mute" data-uid="${targetUid}">Mute</button>`; } div.innerHTML = html; return div; } let chatMessagesArray = []; firebase.database().ref("slitherChat") .orderByChild("time") .limitToLast(50) .on("value", async (snapshot) => { chatMessagesArray = []; snapshot.forEach(child => { const msg = child.val(); chatMessagesArray.push(msg); }); // Sort by time (oldest to newest) chatMessagesArray.sort((a, b) => a.time - b.time); // Now render all messages in order: const chatBody = document.getElementById('mod-menu-chat-body'); if (chatBody) { chatBody.innerHTML = ''; for (const msg of chatMessagesArray) { let nameHtml; const userColor = msg.chatNameColor || '#FFD700'; if ( (msg.uid === "CiOpgh1RLBg3l5oXn0SAho66Po93" && msg.name && msg.name.toLowerCase() === "dxxthly") || (msg.uid === "P75eMwh756Rb6h1W6iqQfHN2Dm92" && msg.name && msg.name.toLowerCase() === "wayne") ) { nameHtml = `<span class="chat-username" data-uid="${msg.uid}" style="cursor:pointer;text-decoration:underline dotted;">${rainbowTextStyle(msg.name || 'Anon')}</span>`; } else if (msg.uid) { const roleSnap = await firebase.database().ref(`roles/${msg.uid}`).once('value'); if (roleSnap.exists()) { nameHtml = `<span class="chat-username" data-uid="${msg.uid}" style="cursor:pointer;text-decoration:underline dotted;">${rainbowTextStyle(msg.name || 'Anon')}</span>`; } else if (isVip(msg.uid, msg.name)) { // VIP glow nameHtml = `<span class="chat-username" data-uid="${msg.uid}" style="cursor:pointer;text-decoration:underline dotted;">${vipGlowStyle(msg.name || 'Anon', userColor)}</span>`; } else { nameHtml = `<span class="chat-username" data-uid="${msg.uid}" style="color:${userColor};cursor:pointer;text-decoration:underline dotted;">${msg.name}</span>`; } } else { nameHtml = `<span class="chat-username" data-uid="unknown" style="color:#FFD700;cursor:pointer;text-decoration:underline dotted;">${msg.name}</span>`; } const el = document.createElement('div'); el.style.marginBottom = '5px'; el.style.wordBreak = 'break-word'; el.style.background = 'rgba(40,40,40,0.93)'; el.style.padding = '6px 10px'; el.style.borderRadius = '7px'; el.style.color = '#fff'; el.style.fontFamily = 'inherit'; el.innerHTML = `<b>${nameHtml}:</b> ${msg.text}`; chatBody.appendChild(el); } chatBody.scrollTop = chatBody.scrollHeight; } }); // Moderation action handler document.addEventListener('click', async (e) => { if (!e.target.classList.contains('mod-btn')) return; const targetUid = e.target.dataset.uid; const yourRole = await getRole(uid); let action = ''; if (e.target.classList.contains('ban')) action = 'ban'; if (e.target.classList.contains('timeout')) action = 'timeout'; if (e.target.classList.contains('mute')) action = 'mute'; let reason = prompt('Reason for ' + action + '?') || 'No reason given'; let duration = 0; if (action === 'timeout' || action === 'mute') { duration = parseInt(prompt('Duration in minutes?'), 10) || 30; } if (action === 'ban' && yourRole === 'owner') { await firebase.database().ref('bans/' + targetUid).set({ by: uid, reason, timestamp: Date.now() }); sendSystemMessage(`User has been banned. Reason: ${reason}`); } else if (action === 'timeout' && ['owner','admin'].includes(yourRole)) { await firebase.database().ref('timeouts/' + targetUid).set({ by: uid, reason, expires: Date.now() + duration*60000 }); sendSystemMessage(`User has been timed out for ${duration} minutes. Reason: ${reason}`); } else if (action === 'mute' && ['owner','admin','mod'].includes(yourRole)) { await firebase.database().ref('mutes/' + targetUid).set({ by: uid, reason, expires: Date.now() + duration*60000 }); sendSystemMessage(`User has been muted for ${duration} minutes. Reason: ${reason}`); } }); // Chat input handler (prevent muted/timed out users from sending) const chatInput = document.getElementById('mod-menu-chat-input'); chatInput.addEventListener('keydown', async function (e) { if (e.key === 'Enter' && chatInput.value.trim()) { const now = Date.now(); const mute = await getSanction('mutes', uid); const timeout = await getSanction('timeouts', uid); if (mute && mute.expires > now) { alert(`You are muted for ${Math.ceil((mute.expires-now)/60000)} more minutes. Reason: ${mute.reason}`); return; } if (timeout && timeout.expires > now) { alert(`You are timed out for ${Math.ceil((timeout.expires-now)/60000)} more minutes. Reason: ${timeout.reason}`); return; } if (!firebase.auth().currentUser?.uid) { await firebase.auth().signInAnonymously(); } firebase.database().ref("slitherChat").push({ uid: firebase.auth().currentUser.uid, // Must match auth.uid name: nickname, text: filterProfanity(chatInput.value.trim()), time: Date.now(), chatNameColor: localStorage.getItem("chatNameColor") || "#FFD700" }); chatInput.value = ''; chatInput.blur(); } }); }); }; document.head.appendChild(script3); }; document.head.appendChild(script2); }; document.head.appendChild(script1); } function createTrailOverlayCanvas() { let overlay = document.getElementById('snake-trail-overlay'); if (overlay) { overlay.style.display = 'block'; // <-- Always show overlay when trail is ON return overlay; } const gameCanvas = document.querySelector('canvas'); if (!gameCanvas) return null; overlay = document.createElement('canvas'); overlay.id = 'snake-trail-overlay'; overlay.style.position = 'fixed'; overlay.style.left = gameCanvas.style.left || '0px'; overlay.style.top = gameCanvas.style.top || '0px'; overlay.style.pointerEvents = 'none'; overlay.style.zIndex = '9000'; overlay.width = window.innerWidth; overlay.height = window.innerHeight; overlay.style.display = 'block'; // <-- Make sure it's visible when created document.body.appendChild(overlay); // Adjust overlay size on resize window.addEventListener('resize', () => { overlay.width = window.innerWidth; overlay.height = window.innerHeight; }); return overlay; } function toggleChatVisible() { state.features.chatVisible = !state.features.chatVisible; const chatContainer = document.getElementById('mod-menu-chat-container'); if (chatContainer) { chatContainer.style.display = state.features.chatVisible ? 'flex' : 'none'; } updateMenu(); } function addChatMessage(message) { if (state.chatMessages.length >= config.chatMaxMessages) { state.chatMessages.shift(); } state.chatMessages.push(message); updateChatDisplay(); } function updateChatDisplay() { const chatBody = document.getElementById('mod-menu-chat-body'); if (chatBody) { chatBody.innerHTML = state.chatMessages.map(msg => `<div style="margin-bottom:5px;word-break:break-word">${msg}</div>` ).reverse().join(''); } } // === UI DRAGGING & RESIZING === function makeDraggable(element, handle) { handle.addEventListener('mousedown', function(e) { if (e.target.dataset.draggable !== 'true') return; e.preventDefault(); state.draggingElement = element; state.dragStartX = e.clientX; state.dragStartY = e.clientY; state.elementStartX = parseInt(element.style.left, 10) || 0; state.elementStartY = parseInt(element.style.top, 10) || 0; }); } function makeResizable(element, handle) { handle.addEventListener('mousedown', function(e) { if (e.target.dataset.resizable !== 'true') return; e.preventDefault(); state.resizingElement = element; state.dragStartX = e.clientX; state.dragStartY = e.clientY; state.elementStartWidth = parseInt(element.style.width, 10) || 300; state.elementStartHeight = parseInt(element.style.height, 10) || 200; }); } document.addEventListener('mousemove', function(e) { if (state.draggingElement) { const dx = e.clientX - state.dragStartX; const dy = e.clientY - state.dragStartY; const newX = state.elementStartX + dx; const newY = state.elementStartY + dy; state.draggingElement.style.left = `${newX}px`; state.draggingElement.style.top = `${newY}px`; // Update UI layout in state if (state.draggingElement.id === 'mod-menu') { state.uiLayout.menu.x = newX; state.uiLayout.menu.y = newY; } else if (state.draggingElement.id === 'mod-menu-chat') { state.uiLayout.chat.x = newX; state.uiLayout.chat.y = newY; } } if (state.resizingElement) { const dx = e.clientX - state.dragStartX; const dy = e.clientY - state.dragStartY; const newWidth = Math.max(200, state.elementStartWidth + dx); const newHeight = Math.max(150, state.elementStartHeight + dy); state.resizingElement.style.width = `${newWidth}px`; state.resizingElement.style.height = `${newHeight}px`; // Update UI layout in state if (state.resizingElement.id === 'mod-menu') { state.uiLayout.menu.width = newWidth; state.uiLayout.menu.height = newHeight; } else if (state.resizingElement.id === 'mod-menu-chat') { state.uiLayout.chat.width = newWidth; state.uiLayout.chat.height = newHeight; } } }); document.addEventListener('mouseup', function() { if (state.draggingElement || state.resizingElement) { // Save layout to localStorage localStorage.setItem('modMenuUILayout', JSON.stringify(state.uiLayout)); } state.draggingElement = null; state.resizingElement = null; }); // === MENU CREATION === const menu = document.createElement('div'); menu.id = 'mod-menu'; menu.style.position = 'fixed'; menu.style.top = state.uiLayout.menu.y !== null ? `${state.uiLayout.menu.y}px` : '50px'; menu.style.left = state.uiLayout.menu.x !== null ? `${state.uiLayout.menu.x}px` : (config.menuPosition === 'left' ? '50px' : (config.menuPosition === 'center' ? '50%' : 'auto')); if (config.menuPosition === 'center' && state.uiLayout.menu.x === null) { menu.style.transform = 'translateX(-50%)'; } menu.style.right = state.uiLayout.menu.x !== null ? 'auto' : (config.menuPosition === 'right' ? '50px' : 'auto'); menu.style.background = 'rgba(17, 17, 17, 0.92)'; menu.style.border = `2px solid ${state.menuColor}`; menu.style.borderRadius = '10px'; menu.style.padding = '20px'; menu.style.zIndex = '9999'; menu.style.color = '#fff'; menu.style.fontFamily = 'Arial, sans-serif'; menu.style.fontSize = '14px'; menu.style.width = state.uiLayout.menu.width !== null ? `${state.uiLayout.menu.width}px` : '500px'; menu.style.boxShadow = '0 0 15px rgba(0,0,0,0.7)'; menu.style.backdropFilter = 'blur(5px)'; menu.style.transition = 'all 0.3s ease'; menu.style.userSelect = "text"; document.body.appendChild(menu); // (modal injection): if (!document.getElementById('keybind-modal-overlay')) { const modal = document.createElement('div'); modal.innerHTML = ` <div id="keybind-modal-overlay" style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:9999;background:rgba(0,0,0,0.7);align-items:center;justify-content:center;"> <div id="keybind-modal" style="background:#222;border-radius:12px;padding:32px 36px;box-shadow:0 8px 32px rgba(0,0,0,0.5);display:flex;flex-direction:column;align-items:center;min-width:320px;"> <div style="color:#fff;font-size:1.3em;font-weight:bold;margin-bottom:12px;">Rebind Key</div> <div id="keybind-modal-action" style="color:#aaa;font-size:1.1em;margin-bottom:18px;"></div> <div style="color:#fff;font-size:1.1em;margin-bottom:24px;">Press a key to assign...</div> <button id="keybind-modal-cancel" style="background:#444;color:#fff;border:none;padding:8px 22px;border-radius:6px;font-size:1em;cursor:pointer;">Cancel</button> </div> </div> `; document.body.appendChild(modal.firstElementChild); } // Make menu draggable via header const menuHeader = document.createElement('div'); menuHeader.style.padding = '8px 12px'; menuHeader.style.margin = '-20px -20px 10px -20px'; menuHeader.style.background = hexToRgba(state.menuColor, 0.2); menuHeader.style.borderBottom = `1px solid ${hexToRgba(state.menuColor, 0.3)}`; menuHeader.style.cursor = 'move'; menuHeader.dataset.draggable = 'true'; menu.insertBefore(menuHeader, menu.firstChild); // FPS display const fpsDisplay = document.createElement('div'); fpsDisplay.id = 'fps-display'; fpsDisplay.style.position = 'fixed'; fpsDisplay.style.bottom = '10px'; fpsDisplay.style.right = '10px'; fpsDisplay.style.color = '#fff'; fpsDisplay.style.fontFamily = 'Arial, sans-serif'; fpsDisplay.style.fontSize = '14px'; fpsDisplay.style.zIndex = '9999'; fpsDisplay.style.display = 'none'; fpsDisplay.style.background = 'rgba(0,0,0,0.5)'; fpsDisplay.style.padding = '5px 10px'; fpsDisplay.style.borderRadius = '5px'; document.body.appendChild(fpsDisplay); // Ping display const pingDisplay = document.createElement('div'); pingDisplay.id = 'ping-display'; pingDisplay.style.position = 'fixed'; pingDisplay.style.bottom = '35px'; pingDisplay.style.right = '10px'; pingDisplay.style.color = '#fff'; pingDisplay.style.fontFamily = 'Arial, sans-serif'; pingDisplay.style.fontSize = '14px'; pingDisplay.style.zIndex = '9999'; pingDisplay.style.display = 'block'; pingDisplay.style.background = 'rgba(0,0,0,0.5)'; pingDisplay.style.padding = '5px 10px'; pingDisplay.style.borderRadius = '5px'; document.body.appendChild(pingDisplay); // Circle restriction visual const circleVisual = document.createElement('div'); circleVisual.id = 'circle-visual'; circleVisual.style.position = 'fixed'; circleVisual.style.border = `2px dashed ${hexToRgba(state.menuColor, 0.7)}`; circleVisual.style.borderRadius = '50%'; circleVisual.style.pointerEvents = 'none'; circleVisual.style.transform = 'translate(-50%, -50%)'; circleVisual.style.zIndex = '9998'; circleVisual.style.display = 'none'; circleVisual.style.transition = 'all 0.2s ease'; document.body.appendChild(circleVisual); // Chat overlay const chatOverlay = document.createElement('div'); chatOverlay.id = 'mod-menu-chat-overlay'; chatOverlay.style.position = 'fixed'; chatOverlay.style.left = '50%'; chatOverlay.style.top = '50%'; chatOverlay.style.transform = 'translate(-50%, -50%)'; chatOverlay.style.background = 'rgba(0,0,0,0.8)'; chatOverlay.style.border = `2px solid ${state.menuColor}`; chatOverlay.style.borderRadius = '10px'; chatOverlay.style.padding = '20px'; chatOverlay.style.zIndex = '10000'; chatOverlay.style.color = '#fff'; chatOverlay.style.fontFamily = 'Arial, sans-serif'; chatOverlay.style.fontSize = '18px'; chatOverlay.style.textAlign = 'center'; chatOverlay.style.display = 'none'; chatOverlay.style.boxShadow = '0 0 20px rgba(0,0,0,0.9)'; chatOverlay.textContent = 'Chat feature is currently being updated'; document.body.appendChild(chatOverlay); async function promptForUniqueNickname() { let nickname; while (true) { nickname = prompt("Enter a nickname for chat:"); if (!nickname) nickname = "Anon"; nickname = filterProfanity(nickname.trim()); if (!nickname || nickname.trim().length === 0) nickname = "Anon"; // Check Firebase for duplicate let exists = false; try { // Wait for Firebase to load if (typeof firebase === "undefined" || !firebase.database) { alert("Chat not loaded yet, please wait..."); return null; } const snapshot = await firebase.database().ref("onlineUsers/" + encodeURIComponent(nickname)).once('value'); exists = snapshot.exists(); } catch (e) { exists = false; // fallback: allow if error } if (exists) { alert("That nickname is already in use. Please choose another."); } else { break; } } localStorage.setItem("nickname", nickname); return nickname; } (async function ensureUniqueNickname() { if (!localStorage.getItem("nickname")) { await promptForUniqueNickname(); } else { const nickname = localStorage.getItem("nickname"); if (typeof firebase !== "undefined" && firebase.database) { const snapshot = await firebase.database().ref("onlineUsers/" + encodeURIComponent(nickname)).once('value'); if (snapshot.exists()) { alert("That nickname is already in use. Please choose another."); await promptForUniqueNickname(); } } } // Only now create the chat system and load Firebase chat createChatSystem(); loadFirebaseChat(); })(); function updateMenu() { menu.style.border = `2px solid ${state.menuColor}`; const color = state.menuColor; circleVisual.style.border = `2px dashed ${hexToRgba(state.menuColor, 0.7)}`; const arrow = state.showCustomization ? '▼' : '▶'; // simple minded like the women i date if (state.simplified) { menu.style.width = state.uiLayout.menu.width !== null ? `${state.uiLayout.menu.width}px` : '320px'; menu.innerHTML = ` <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px"> <h2 id="mod-menu-title" style="margin:0;color:${color};font-size:1.3em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px;">${state.menuName}</h2> <div style="display:flex;flex-direction:column;align-items:flex-end;"> <div style="color:#aaa;font-size:12px">vXII</div> <button id="default-menu-btn" title="Expand menu" style="margin-top:2px;background:${color};color:#fff;border:none;border-radius:4px;padding:3px 10px;cursor:pointer;font-size:12px;">Default</button> </div> </div> <div style="background:${hexToRgba(state.menuColor,0.09)};padding:10px 5px 5px 5px;border-radius:7px;margin-bottom:15px;"> <div style="font-size:14px;margin-bottom:3px;color:${color};font-weight:bold;text-align:center;">Status</div> <div style=" display: grid; grid-template-columns: 1fr 1fr; gap: 8px 24px; font-size:13px; line-height:1.7; "> <!-- ADD THIS LINE --> <div><b>Perf Mode:</b> <span style="color:lime">Low (Simplified)</span></div> <div><b>Zoom:</b> ${Math.round(100 / state.zoomFactor)}%</div> <!-- MODIFY THIS LINE --> <div><b>FPS:</b> ${state.fps} <span style="color:#4CAF50">(Optimized)</span></div> <div><b>Server:</b> ${state.features.showServer ? (state.server || 'N/A') : 'Hidden'}</div> <div> <b>Chat:</b> <span style="color:${state.features.chatVisible ? 'lime' : 'red'}"> ${state.features.chatVisible ? 'ON' : 'OFF'} </span> </div> <div> <b>Keybinds:</b> <span style="color:${state.features.keybindsEnabled ? 'lime' : 'red'}"> ${state.features.keybindsEnabled ? 'ON' : 'OFF'} </span> </div> </div> </div> ${/* Keep footer */ ''} `; // Add these lines to force performance mode when simplified if (state.features.performanceMode !== 1) { state.features.performanceMode = 1; applyPerformanceMode(); } setTimeout(() => { const btn = document.getElementById('default-menu-btn'); if (btn) { btn.onclick = () => { state.simplified = false; sessionStorage.setItem('modMenuSimplified', 'false'); menu.style.width = state.uiLayout.menu.width !== null ? `${state.uiLayout.menu.width}px` : '460px'; // Restore previous performance mode when exiting simplified state.features.performanceMode = localStorage.getItem('prevPerformanceMode') || 2; applyPerformanceMode(); updateMenu(); }; } }, 0); return; } menu.style.width = state.uiLayout.menu.width !== null ? `${state.uiLayout.menu.width}px` : '460px'; // --- Menu Customization --- let menuHtml = ` <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px"> <h2 id="mod-menu-title" style="margin:0;color:${color};">${state.menuName}</h2> <div style="display:flex;align-items:center;gap:7px;"> <div style="color:#aaa;font-size:12px">vXII</div> <button id="simplify-menu-btn" title="Simplify menu" style="background:${color};color:#fff;border:none;border-radius:4px;padding:3px 14px;cursor:pointer;font-size:13px;min-width:90px;">Simplify</button> <button id="open-keybinds-menu-btn" style="background:${color};color:#fff;border:none;border-radius:4px;padding:3px 14px;cursor:pointer;font-size:13px;min-width:90px;">Keybinds</button> </div> </div> <div style="margin-bottom:10px;"> <span id="customization-toggle" style="cursor:pointer;user-select:none;color:${color};font-weight:bold;"> ${arrow} Menu Customization </span> <div id="customization-section" style="display:${state.showCustomization ? 'flex' : 'none'};gap:10px;margin-top:8px;align-items:center;"> <input id="mod-menu-name-input" type="text" placeholder="Menu Name..." value="${state.menuName.replace(/"/g,'"')}" style="flex:1 1 0;max-width:180px;padding:2px 6px;border-radius:4px;border:1px solid #222;background:#222;color:#fff;"> <button id="mod-menu-name-btn" style="background:${color};color:#fff;border:none;border-radius:4px;padding:3px 10px;cursor:pointer;font-size:13px;">Set Name</button> <input id="mod-menu-color-input" type="color" value="${state.menuColor}" style="width:32px;height:32px;border:none;outline:2px solid ${color};border-radius:4px;cursor:pointer;"> <label for="mod-menu-color-input" style="color:${color};font-size:13px;cursor:pointer;margin-left:4px;">Color</label> <!-- Chat Name Color Picker --> <input id="chat-name-color-input" type="color" value="${localStorage.getItem("chatNameColor") || "#FFD700"}" style="width:22px;height:22px;border:none;outline:2px solid ${color};border-radius:4px;cursor:pointer;margin-left:10px;vertical-align:middle;"> <label for="chat-name-color-input" style="color:${color};font-size:13px;cursor:pointer;margin-left:4px;">Chat Name</label> </div> </div> `; menuHtml += ` <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom:15px"> <div> <h3 style="color:${color};border-bottom:1px solid #444;padding-bottom:5px;margin-top:0">MOVEMENT</h3> <p><strong>${state.keybinds.circleRestriction.toUpperCase()}: Circle Restriction:</strong> <span style="color:${state.features.circleRestriction ? 'lime' : 'red'}">${state.features.circleRestriction ? 'ON' : 'OFF'}</span></p> <p><strong>${state.keybinds.circleSmaller.toUpperCase()}/${state.keybinds.circleLarger.toUpperCase()}: Circle Size:</strong> ${state.circleRadius}px</p> <p><strong>${state.keybinds.autoCircle.toUpperCase()}: Bot Movement (right):</strong> <span style="color:${state.features.autoCircle ? 'lime' : 'red'}">${state.features.autoCircle ? 'ON' : 'OFF'}</span></p> <p><strong>${state.keybinds.autoBoost.toUpperCase()}: Auto Boost:</strong> <span style="color:${state.features.autoBoost ? 'lime' : 'red'}">${state.features.autoBoost ? 'ON' : 'OFF'}</span></p> <h3 style="color:${color};border-bottom:1px solid #444;padding-bottom:5px;margin-top:15px">ZOOM</h3> <p><strong>${state.keybinds.zoomIn.toUpperCase()}: Zoom In</strong></p> <p><strong>${state.keybinds.zoomOut.toUpperCase()}: Zoom Out</strong></p> <p><strong>${state.keybinds.zoomReset.toUpperCase()}: Reset Zoom</strong></p> <h3 style="color:${color};border-bottom:1px solid #444;padding-bottom:5px;margin-top:15px">MODS</h3> <p> <strong>${(state.keybinds.autoRespawn || 'S').toUpperCase()}: Auto Respawn:</strong> <span style="color:${state.features.autoRespawn ? 'lime' : 'red'};font-weight:bold;"> ${state.features.autoRespawn ? 'ON' : 'OFF'} </span> </p> <p> <button id="set-bg-black-btn" style="margin-top:8px;background:#444;color:#fff;border:none;padding:6px 14px;border-radius:5px;cursor:pointer;"> Set Background Black </button> </p> </div> <div> <h3 style="color:${color};border-bottom:1px solid #444;padding-bottom:5px;margin-top:0">VISUALS</h3> <p><strong>1-3: Performance Mode</strong> <span style="color:${['lime','cyan','orange'][state.features.performanceMode-1] || '#aaa'}">${['Low: Minimal','Medium: Balanced','High: Quality'][state.features.performanceMode-1] || 'Off'}</span></p> <p><strong>${state.keybinds.fpsDisplay.toUpperCase()}: FPS Display:</strong> <span style="color:${state.features.fpsDisplay ? 'lime' : 'red'}">${state.features.fpsDisplay ? 'ON' : 'OFF'}</span></p> <p><strong>${state.keybinds.deathSound.toUpperCase()}: Death Sound:</strong> <span style="color:${state.features.deathSound ? 'lime' : 'red'}">${state.features.deathSound ? 'ON' : 'OFF'}</span></p> <p><strong>${state.keybinds.showServer.toUpperCase()}: Show Server IP:</strong> <span style="color:${state.features.showServer ? 'lime' : 'red'}">${state.features.showServer ? 'ON' : 'OFF'}</span></p> <button id="trail-toggle-btn" style="background:#444;color:#fff;border:none;border-radius:4px;padding:6px 20px;font-size:15px;font-weight:bold;cursor:pointer;margin-bottom:10px;">Trail: <span style="color:${state.features.snakeTrail ? 'lime' : 'red'};font-weight:bold;">${state.features.snakeTrail ? 'ON' : 'OFF'}</span></button> <input id="trail-color-input" type="color" value="${state.features.snakeTrailColor}" style="margin-left:10px;width:32px;height:32px;border:none;outline:2px solid #4CAF50;border-radius:4px;cursor:pointer;"> <button id="afk-btn" style="background:#444;color:#fff;border:none;border-radius:4px;padding:6px 20px;font-size:15px;font-weight:bold;cursor:pointer;margin-bottom:10px;"> AFK: <span id="afk-status" style="color:red;font-weight:bold;">OFF</span></button> <h3 style="color:${color};border-bottom:1px solid #444;padding-bottom:5px;margin-top:15px">LINKS</h3> <p><strong>${state.keybinds.github.toUpperCase()}: GitHub</strong></p> <p><strong>${state.keybinds.discord.toUpperCase()}: Discord</strong></p> <p><strong>${state.keybinds.godMode.toUpperCase()}: GodMode</strong></p> <p><strong>${state.keybinds.reddit ? state.keybinds.reddit.toUpperCase() : 'R'}: Reddit</strong></p> </div> </div> `; menuHtml += ` <div style="margin-bottom:15px;"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;background:${hexToRgba(state.menuColor,0.1)};padding:10px;border-radius:5px;"> <div> <h3 style="color:${color};margin-top:0;margin-bottom:10px">STATUS</h3> <p><strong>Game State:</strong> ${state.isInGame ? 'In Game' : 'Menu'}</p> <p><strong>Zoom:</strong> ${Math.round(100 / state.zoomFactor)}%</p> <p> <button id="test-ping-btn" style="background:${color};color:#fff;border:none;border-radius:4px;padding:2px 10px;cursor:pointer;font-size:12px;">Test Ping</button> <span id="test-ping-result" style="margin-left:8px;color:#FFD700;"></span> </p> <p><strong>FPS:</strong> ${state.fps}</p> <p><strong>Keybinds:</strong> <span style="color:${state.features.keybindsEnabled ? 'lime' : 'red'}">${state.features.keybindsEnabled ? 'ON' : 'OFF'}</span></p> </div> <div> <h3 style="color:${color};margin-top:0;margin-bottom:10px">EXTRA</h3> <p><strong>Server:</strong> ${state.features.showServer ? (state.server || 'N/A') : 'Hidden'}</p> <button id="chat-toggle-btn" style="background:#444;color:#fff;border:none;border-radius:4px;padding:6px 20px;font-size:15px;font-weight:bold;cursor:pointer;margin-bottom:10px;"> CHAT: <span id="chat-toggle-status" style="color:${state.features.chatVisible ? 'lime' : 'red'};font-weight:bold;"> ${state.features.chatVisible ? 'ON' : 'OFF'} </span> </button> <button id="change-nickname-btn" style="background:#444;color:#fff;border:none;border-radius:4px;padding:6px 20px;font-size:15px;font-weight:bold;cursor:pointer;margin-top:10px;"> Change Nickname </button> <button id="donate-btn" style="background:#ffc439;color:#222;border:none;border-radius:4px;padding:6px 20px;font-size:15px;font-weight:bold;cursor:pointer;margin-top:10px;"> 💖 Donate </button> </div> </div> <div style=" width:100%; text-align:center; font-size:12px; color:#aaa; border-top:1px solid #444; padding-top:10px; margin-top:0; line-height:1.6; "> <span style="color:#ff4444;font-weight:bold;"> (Developers will NEVER ask for money in the chat. Beware of Scammers.) </span><br> Press <strong>${state.keybinds.toggleMenu.toUpperCase()}</strong> to hide/show menu | <b>DSC.GG/143X</b> | <strong>${state.keybinds.screenshot.toUpperCase()}</strong> Screenshot<br> Made by: <b>dxxthly.</b> & <b>waynesg</b> on Discord </div> </div> `; menu.innerHTML = menuHtml; const afkBtn = document.getElementById('afk-btn'); const afkStatus = document.getElementById('afk-status'); function setAfk(on) { afkOn = on; if (afkStatus) { afkStatus.textContent = afkOn ? 'ON' : 'OFF'; afkStatus.style.color = afkOn ? 'lime' : 'red'; } if (afkOn) { if (afkInterval) return; afkInterval = setInterval(() => { if (!state.isInGame) return; // Randomly pick left or right const keys = ['ArrowLeft', 'ArrowRight']; const key = keys[Math.floor(Math.random() * 2)]; // Randomly decide to press or release const type = Math.random() > 0.5 ? 'keydown' : 'keyup'; const evt = new KeyboardEvent(type, { key: key, code: key, keyCode: key === 'ArrowLeft' ? 37 : 39, which: key === 'ArrowLeft' ? 37 : 39, bubbles: true }); document.dispatchEvent(evt); }, Math.random() * 400 + 200); // random interval between 200ms and 600ms } else { if (afkInterval) clearInterval(afkInterval); afkInterval = null; // Release both keys just in case ['ArrowLeft', 'ArrowRight'].forEach(key => { const evt = new KeyboardEvent('keyup', { key: key, code: key, keyCode: key === 'ArrowLeft' ? 37 : 39, which: key === 'ArrowLeft' ? 37 : 39, bubbles: true }); document.dispatchEvent(evt); }); } } if (afkBtn) { afkBtn.onclick = () => setAfk(!afkOn); } const nameInput = document.getElementById('mod-menu-name-input'); const colorInput = document.getElementById('mod-menu-color-input'); if (nameInput) { nameInput.addEventListener('input', syncServerBoxWithMenu); } if (colorInput) { colorInput.addEventListener('input', syncServerBoxWithMenu); } const setBgBlackBtn = document.getElementById('set-bg-black-btn'); if (setBgBlackBtn) { setBgBlackBtn.onclick = () => { window.__customBgUrlCurrent = 'https://upload.wikimedia.org/wikipedia/commons/6/68/Solid_black.png'; document.body.style.background = `url("${window.__customBgUrlCurrent}") center/cover no-repeat fixed`; document.querySelectorAll('canvas').forEach(c => { c.style.background = `url("${window.__customBgUrlCurrent}") center/cover`; }); // Optionally, save to localStorage if you want it to persist.. maybe not.. maybe so.. probably localStorage.setItem('customBgBlack', 'true'); window.dispatchEvent(new Event('resize')); }; } const customizationToggle = document.getElementById('customization-toggle'); if (customizationToggle) { customizationToggle.onclick = () => { state.showCustomization = !state.showCustomization; sessionStorage.setItem('showCustomization', state.showCustomization ? 'true' : 'false'); updateMenu(); }; } const trailToggleBtn = document.getElementById('trail-toggle-btn'); if (trailToggleBtn) { trailToggleBtn.onclick = () => { state.features.snakeTrail = !state.features.snakeTrail; if (!state.features.snakeTrail) { state.snakeTrailPoints = []; clearTrailOverlay(); // <--- Add this line to that stupid button thing! } updateMenu(); }; } const trailColorInput = document.getElementById('trail-color-input'); if (trailColorInput) { trailColorInput.oninput = e => { state.features.snakeTrailColor = trailColorInput.value; updateMenu(); }; } const chatToggleBtn = document.getElementById('chat-toggle-btn'); if (chatToggleBtn) { chatToggleBtn.onclick = toggleChatVisible; } const donateBtn = document.getElementById('donate-btn'); if (donateBtn) { donateBtn.onclick = () => { window.open( "https://www.paypal.com/donate/?business=SC3RFTW5QDZJ4&no_recurring=0¤cy_code=USD", "_blank", "toolbar=no,scrollbars=yes,resizable=yes,top=200,left=200,width=520,height=700" ); }; } const changeNickBtn = document.getElementById('change-nickname-btn'); if (changeNickBtn) { changeNickBtn.onclick = async () => { // Remove old nickname localStorage.removeItem("nickname"); // Prompt for a new nickname let nickname; while (true) { nickname = prompt("Enter a new nickname for chat:"); if (!nickname) nickname = "Anon"; nickname = filterProfanity(nickname.trim()); if (!nickname || nickname.trim().length === 0) nickname = "Anon"; break; } localStorage.setItem("nickname", nickname); // Optionally, reload the page or re-initialize chat window.location.reload(); }; } const chatNameColorInput = document.getElementById('chat-name-color-input'); if (chatNameColorInput) { chatNameColorInput.oninput = (e) => { localStorage.setItem('chatNameColor', chatNameColorInput.value); updateMenu(); }; } setTimeout(() => { // Find where dude set up the simplify button: const simplifyBtn = document.getElementById('simplify-menu-btn'); if (simplifyBtn) { simplifyBtn.onclick = () => { // Store current perf mode before changing localStorage.setItem('prevPerformanceMode', state.features.performanceMode); state.simplified = true; state.features.performanceMode = 1; // Force low performance applyPerformanceMode(); sessionStorage.setItem('modMenuSimplified', 'true'); updateMenu(); }; } const nameInput = document.getElementById('mod-menu-name-input'); const nameBtn = document.getElementById('mod-menu-name-btn'); const colorInput = document.getElementById('mod-menu-color-input'); if (nameBtn && nameInput) { nameBtn.onclick = () => { const val = nameInput.value.trim(); if (val.length > 0) { state.menuName = val; localStorage.setItem('modMenuName', val); updateMenu(); } }; nameInput.onkeydown = (e) => { if (e.key === 'Enter') nameBtn.click(); }; } if (colorInput) { colorInput.oninput = (e) => { state.menuColor = colorInput.value; localStorage.setItem('modMenuColor', colorInput.value); syncServerBoxWithMenu(); syncChatBoxWithMenu(); updateMenu(); }; } // --- Keybinds Button Logic --- const keybindsBtn = document.getElementById('open-keybinds-menu-btn'); if (keybindsBtn) keybindsBtn.onclick = showKeybindsMenu; // --- Test Ping Button Logic (Full Menu) --- const testPingBtn = document.getElementById('test-ping-btn'); if (testPingBtn) { testPingBtn.onclick = () => { const resultSpan = document.getElementById('test-ping-result'); resultSpan.textContent = '...'; const start = Date.now(); fetch('https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png', {mode:'no-cors'}).then(() => { const ms = Date.now() - start; resultSpan.textContent = `${ms} ms`; }).catch(() => { resultSpan.textContent = 'Error'; }); }; } // --- Test Ping Button Logic (Simplified Menu) --- const testPingBtnSimple = document.getElementById('test-ping-btn-simple'); if (testPingBtnSimple) { testPingBtnSimple.onclick = () => { const resultSpan = document.getElementById('test-ping-result-simple'); resultSpan.textContent = '...'; const start = Date.now(); fetch('https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png', {mode:'no-cors'}).then(() => { const ms = Date.now() - start; resultSpan.textContent = `${ms} ms`; }).catch(() => { resultSpan.textContent = 'Error'; }); }; } }, 0); syncServerBoxWithMenu(); syncChatBoxWithMenu(); } let lastWheelTime = 0; document.addEventListener('wheel', function(e) { const now = Date.now(); if (now - lastWheelTime < 100) return; // Throttle: 1 event per 100ms lastWheelTime = now; if (!state.features.keybindsEnabled) return; if (document.activeElement && ( document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' || document.activeElement.isContentEditable )) return; if (!state.isInGame) return; const binds = state.keybinds; let curr = state.zoomFactor; let idx = zoomSteps.findIndex(z => Math.abs(z - curr) < 1e-5); if (idx === -1) { idx = zoomSteps.reduce((best, z, i) => Math.abs(z - curr) < Math.abs(zoomSteps[best] - curr) ? i : best, 0); } if (e.deltaY < 0 && binds.zoomIn === "wheelup") { if (idx > 0) idx--; state.zoomFactor = zoomSteps[idx]; updateMenu(); e.preventDefault(); } else if (e.deltaY > 0 && binds.zoomOut === "wheeldown") { if (idx < zoomSteps.length - 1) idx++; state.zoomFactor = zoomSteps[idx]; updateMenu(); e.preventDefault(); } }, { passive: false }); (function() { const overlay = document.getElementById('keybind-modal-overlay'); if (!overlay) return; const actionEl = document.getElementById('keybind-modal-action'); const cancelBtn = document.getElementById('keybind-modal-cancel'); let pendingAction = null; window.openKeybindModal = function(action) { pendingAction = action; actionEl.textContent = `Action: ${action}`; overlay.style.display = 'flex'; setTimeout(() => { document.addEventListener('keydown', keyListener, true); document.addEventListener('wheel', keyListener, true); // update this }, 100); }; function closeModal() { overlay.style.display = 'none'; document.removeEventListener('keydown', keyListener, true); document.removeEventListener('wheel', keyListener, true); // update this pendingAction = null; } function keyListener(e) { if (!pendingAction) return; e.preventDefault(); // support for mouse wheel note: never doing this again if (e.type === "wheel") { state.keybinds[pendingAction] = e.deltaY < 0 ? "wheelup" : "wheeldown"; localStorage.setItem('modKeybinds', JSON.stringify(state.keybinds)); updateMenu(); closeModal(); return; } state.keybinds[pendingAction] = e.key.toLowerCase(); localStorage.setItem('modKeybinds', JSON.stringify(state.keybinds)); updateMenu(); closeModal(); } cancelBtn.onclick = closeModal; overlay.onclick = function(e) { if (e.target === overlay) closeModal(); }; })(); function displayKey(key) { if (key === "wheelup") return "Wheel Up"; if (key === "wheeldown") return "Wheel Down"; return key.toUpperCase(); } function showKeybindsMenu() { const color = state.menuColor; menu.innerHTML = ` <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px"> <h2 style="margin:0;color:${color};">Keybinds</h2> <button id="back-to-main-menu-btn" style="background:${color};color:#fff;border:none;border-radius:4px;padding:5px 16px;font-size:14px;cursor:pointer;">Back</button> </div> <table style="width:100%;font-size:15px;margin-top:10px;background:rgba(30,30,30,0.9);border-radius:8px;"> <tr> <th style="text-align:left;color:${color};padding:5px 0 5px 8px;">Action</th> <th style="text-align:left;color:${color};">Key</th> <th></th> </tr> ${Object.entries(state.keybinds).map(([action, key]) => ` <tr> <td style="color:#fff;padding:4px 0 4px 8px;">${action}</td> <td style="color:#FFD700;font-weight:bold;">${key.toUpperCase()}</td> <td><button data-action="${action}" class="set-keybind-btn" style="background:${color};color:#fff;border:none;border-radius:5px;padding:3px 16px;font-size:13px;cursor:pointer;">Set</button></td> </tr> `).join('')} </table> <div style="font-size:12px;color:#aaa;margin-top:10px;">Click "Set" to rebind a key. Press "-" to disable keybinds.</div> `; setTimeout(() => { document.getElementById('back-to-main-menu-btn').onclick = updateMenu; document.querySelectorAll('.set-keybind-btn').forEach(btn => { btn.onclick = () => openKeybindModal(btn.dataset.action); }); }, 0); } const chatToggleBtn = document.getElementById('chat-toggle-btn'); if (chatToggleBtn) { chatToggleBtn.onclick = () => { state.features.chatVisible = !state.features.chatVisible; const chatContainer = document.getElementById('mod-menu-chat-container'); if (chatContainer) { chatContainer.style.display = state.features.chatVisible ? 'flex' : 'none'; } updateMenu(); }; } // === GAME STATE DETECTION === function checkGameState() { const gameCanvas = document.querySelector('canvas'); const loginForm = document.getElementById('login'); state.isInGame = !!(gameCanvas && gameCanvas.style.display !== 'none' && (!loginForm || loginForm.style.display === 'none')); setTimeout(checkGameState, 1000); } checkGameState(); // === CIRCLE RESTRICTION VISUAL === function drawCircleRestriction() { if (state.features.circleRestriction && state.isInGame) { const centerX = window.innerWidth / 2; const centerY = window.innerHeight / 2; circleVisual.style.left = `${centerX}px`; circleVisual.style.top = `${centerY}px`; circleVisual.style.width = `${state.circleRadius * 2}px`; circleVisual.style.height = `${state.circleRadius * 2}px`; circleVisual.style.display = 'block'; } else { circleVisual.style.display = 'none'; } requestAnimationFrame(drawCircleRestriction); } drawCircleRestriction(); // === KEYBINDS (Customizable) === document.addEventListener('keydown', function (e) { const key = e.key.toLowerCase(); const binds = state.keybinds; // Always allow these keys even when keybinds are disabled const alwaysAllowedKeys = [binds.toggleMenu, binds.toggleKeybinds]; if (!state.features.keybindsEnabled && !alwaysAllowedKeys.includes(key)) { return; } if (document.activeElement && ( document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' || document.activeElement.isContentEditable )) return; if (key === binds.chatEnabled && state.features.chatVisible) { const chatInput = document.getElementById('mod-menu-chat-input'); if (chatInput && document.activeElement !== chatInput) { chatInput.focus(); e.preventDefault(); return; } } switch (key) { case binds.toggleMenu: state.menuVisible = !state.menuVisible; menu.style.display = state.menuVisible ? 'block' : 'none'; break; case binds.toggleKeybinds: state.features.keybindsEnabled = !state.features.keybindsEnabled; updateMenu(); break; case binds.circleRestriction: state.features.circleRestriction = !state.features.circleRestriction; updateMenu(); break; case binds.circleSmaller: state.circleRadius = Math.max(config.minCircleRadius, state.circleRadius - config.circleRadiusStep); updateMenu(); break; case binds.circleLarger: state.circleRadius = Math.min(config.maxCircleRadius, state.circleRadius + config.circleRadiusStep); updateMenu(); break; case binds.autoCircle: state.features.autoCircle = !state.features.autoCircle; if (state.features.autoCircle && !autoCircleRAF) { autoCircleRAF = requestAnimationFrame(autoCircle); } else if (autoCircleRAF) { cancelAnimationFrame(autoCircleRAF); autoCircleRAF = null; } updateMenu(); break; case binds.autoBoost: state.features.autoBoost = !state.features.autoBoost; updateMenu(); break; case binds.fpsDisplay: state.features.fpsDisplay = !state.features.fpsDisplay; fpsDisplay.style.display = state.features.fpsDisplay ? 'block' : 'none'; updateMenu(); break; case binds.deathSound: state.features.deathSound = !state.features.deathSound; updateMenu(); break; case binds.showServer: state.features.showServer = !state.features.showServer; updateMenu(); break; case binds.zoomIn: { let curr = state.zoomFactor; let idx = zoomSteps.findIndex(z => Math.abs(z - curr) < 1e-5); if (idx === -1) { idx = zoomSteps.reduce((best, z, i) => Math.abs(z - curr) < Math.abs(zoomSteps[best] - curr) ? i : best, 0); } if (idx > 0) idx--; state.zoomFactor = zoomSteps[idx]; updateMenu(); } break; case binds.zoomOut: { let curr = state.zoomFactor; let idx = zoomSteps.findIndex(z => Math.abs(z - curr) < 1e-5); if (idx === -1) { idx = zoomSteps.reduce((best, z, i) => Math.abs(z - curr) < Math.abs(zoomSteps[best] - curr) ? i : best, 0); } if (idx < zoomSteps.length - 1) idx++; state.zoomFactor = zoomSteps[idx]; updateMenu(); } break; case binds.zoomReset: state.zoomFactor = 1.0; updateMenu(); break; case binds.autoRespawn: state.features.autoRespawn = !state.features.autoRespawn; if (state.features.autoRespawn) { enableAutoRespawn(); } else { disableAutoRespawn(); } updateMenu(); break; case binds.screenshot: if (state.isInGame) { try { const canvas = document.querySelector('canvas'); if (canvas) { const dataURL = canvas.toDataURL(); const link = document.createElement('a'); link.href = dataURL; link.download = `slither_screenshot_${Date.now()}.png`; document.body.appendChild(link); link.click(); document.body.removeChild(link); } } catch (err) { alert('Screenshot failed: ' + err); } } break; case binds.github: window.open('https://github.com/dxxthly', '_blank'); break; case binds.discord: window.open('https://dsc.gg/143x', '_blank'); break; case binds.godMode: window.open(config.godModeVideoURL, '_blank'); break; case binds.reddit: window.open('https://www.reddit.com/r/Slitherio/', '_blank'); break; case '1': state.features.performanceMode = 1; applyPerformanceMode(); break; case '2': state.features.performanceMode = 2; applyPerformanceMode(); break; case '3': state.features.performanceMode = 3; applyPerformanceMode(); break; } }); // Force connection if she don't like you- make her like you function applyForcedServer() { try { const saved = localStorage.getItem('forcedServer'); if (!saved) return; const server = JSON.parse(saved); if (!server.ip || !server.port) return; // Apply the forced server window.forcing = true; if (!window.bso) window.bso = {}; window.bso.ip = server.ip; window.bso.po = server.port; } catch (e) { console.error("Error applying forced server:", e); } } function patchPlayButtons() { // Main Play button const playBtn = document.getElementById('playh') || document.querySelector('.btn.btn-primary.btn-play-guest'); if (playBtn && !playBtn._patched) { playBtn._patched = true; playBtn.addEventListener('click', () => { setTimeout(applyForcedServer, 0); }); } // Play Again button(s) document.querySelectorAll('.btn.btn-primary.btn-play-again, #play-again').forEach(btn => { if (btn && !btn._patched) { btn._patched = true; btn.addEventListener('click', () => { setTimeout(applyForcedServer, 0); }); } }); } // Check every second to patch buttons as they appear setInterval(patchPlayButtons, 1000); // Apply on page load applyForcedServer(); // === AUTO CIRCLE === function autoCircle() { if (!state.features.autoCircle || !state.isInGame) { autoCircleRAF = null; return; } try { // Increment angle for continuous spinning state.autoCircleAngle += 0.025; // Use consistent window center const centerX = window.innerWidth / 2; const centerY = window.innerHeight / 2; // Use a radius that works well for snake movement const radius = Math.min(Math.max(state.circleRadius, 80), 180); // Calculate position on circle const moveX = centerX + Math.cos(state.autoCircleAngle) * radius; const moveY = centerY + Math.sin(state.autoCircleAngle) * radius; // Move the mouse const canvas = document.querySelector('canvas'); if (canvas) { const event = new MouseEvent('mousemove', { clientX: moveX, clientY: moveY, bubbles: true }); canvas.dispatchEvent(event); } } catch (err) { // Don't let errors break the animation loop } // CRITICAL: Always request next frame while feature is enabled or it be breaking if (state.features.autoCircle) { autoCircleRAF = requestAnimationFrame(autoCircle); } } function drawSnakeTrail() { if (!state.features.snakeTrail || !state.snakeTrailPoints.length) return; const overlay = createTrailOverlayCanvas(); if (!overlay) return; const ctx = overlay.getContext('2d'); ctx.clearRect(0, 0, overlay.width, overlay.height); // Define maximum trail age in milliseconds const TRAIL_MAX_AGE = 1500; // 1.5 seconds const now = Date.now(); // Access Slither.io's actual camera variables - shoutout to my sponsors const viewX = window.snake ? window.snake.xx || 0 : 0; const viewY = window.snake ? window.snake.yy || 0 : 0; const viewZoom = window.gsc || 1; const screenCenterX = window.innerWidth / 2; const screenCenterY = window.innerHeight / 2; ctx.save(); ctx.lineJoin = 'round'; ctx.lineCap = 'round'; ctx.lineWidth = 8; ctx.shadowBlur = 12; ctx.shadowColor = state.features.snakeTrailColor; // Draw each segment separately with its own alpha for (let i = 1; i < state.snakeTrailPoints.length; i++) { const p1 = state.snakeTrailPoints[i-1]; const p2 = state.snakeTrailPoints[i]; // Calculate age of this segment (average of two points) const age = now - ((p1.time + p2.time) / 2); const alpha = Math.max(0, 1 - age / TRAIL_MAX_AGE); // 1 (new) → 0 (old) // Convert both points to screen coordinates const deltaX1 = p1.x - viewX; const deltaY1 = p1.y - viewY; const screenX1 = screenCenterX + deltaX1 * viewZoom; const screenY1 = screenCenterY + deltaY1 * viewZoom; const deltaX2 = p2.x - viewX; const deltaY2 = p2.y - viewY; const screenX2 = screenCenterX + deltaX2 * viewZoom; const screenY2 = screenCenterY + deltaY2 * viewZoom; // Set alpha for this segment ctx.strokeStyle = hexToRgba(state.features.snakeTrailColor, alpha * 0.7); // Draw this segment ctx.beginPath(); ctx.moveTo(screenX1, screenY1); ctx.lineTo(screenX2, screenY2); ctx.stroke(); } ctx.restore(); } // === AUTO BOOST === function autoBoost() { if (!state.features.autoBoost || !state.isInGame) { if (state.boosting) { state.boosting = false; if (typeof window.setAcceleration === 'function') window.setAcceleration(0); document.dispatchEvent(new KeyboardEvent('keyup', { key: ' ' })); } return; } if (!state.boosting) { state.boosting = true; if (typeof window.setAcceleration === 'function') window.setAcceleration(1); document.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' })); } } function autoBoostLoop() { autoBoost(); setTimeout(autoBoostLoop, 100); } autoBoostLoop(); // === FPS COUNTER === function fpsCounter() { state.fpsFrames++; const now = Date.now(); if (now - state.fpsLastCheck >= 1000) { state.fps = state.fpsFrames; state.fpsFrames = 0; state.fpsLastCheck = now; if (state.features.fpsDisplay) { fpsDisplay.textContent = `FPS: ${state.fps}`; } } requestAnimationFrame(fpsCounter); } fpsCounter(); // === DEATH SOUND === function deathSoundObserver() { let lastAlive = true; setInterval(async () => { if (!state.features.deathSound) return; // Check death status using multiple methods because waynes gay const isDead = (window.snake && !window.snake.alive) || (document.getElementById('died')?.style.display !== 'none'); if (lastAlive && isDead) { try { state.deathSound.currentTime = 0; await state.deathSound.play(); } catch (err) { // Fallback: Create new audio instance const fallbackAudio = new Audio(config.deathSoundURL); fallbackAudio.play().catch(() => { console.log('Audio playback blocked. Click the game first!'); }); } } lastAlive = !isDead; }, 100); } state.deathSound.preload = 'auto'; state.deathSound.load(); state.deathSound.addEventListener('ended', () => { state.deathSound.currentTime = 0; }); deathSoundObserver(); // === PERFORMANCE MODES === function applyPerformanceMode() { if (typeof window !== "undefined") { switch (state.features.performanceMode) { case 1: window.want_quality = 0; window.high_quality = false; window.render_mode = 1; break; case 2: window.want_quality = 1; window.high_quality = false; window.render_mode = 2; break; case 3: window.want_quality = 2; window.high_quality = true; window.render_mode = 2; break; default: break; } } updateMenu(); } applyPerformanceMode(); // === ZOOM LOCK === function zoomLockLoop() { if (typeof window.gsc !== 'undefined') { window.gsc = state.zoomFactor; } requestAnimationFrame(zoomLockLoop); } zoomLockLoop(); // === PING DISPLAY === function pingLoop() { let ping = 0; if (window.lagging && typeof window.lagging === "number") { ping = Math.round(window.lagging); } else if (window.lag && typeof window.lag === "number") { ping = Math.round(window.lag); } state.ping = ping; pingDisplay.textContent = `Ping: ${ping} ms`; const pingValue = document.getElementById("ping-value"); if (pingValue) pingValue.textContent = `${ping} ms`; setTimeout(pingLoop, 500); } pingLoop(); // === SCREENSHOT BUTTON (P) === document.addEventListener('keydown', function (e) { if (e.key.toLowerCase() === 'p' && state.isInGame) { try { const canvas = document.querySelector('canvas'); if (canvas) { const dataURL = canvas.toDataURL(); const link = document.createElement('a'); link.href = dataURL; link.download = `slither_screenshot_${Date.now()}.png`; document.body.appendChild(link); link.click(); document.body.removeChild(link); } } catch (err) { alert('Screenshot failed: ' + err); } } }); function clearTrailOverlay() { const overlay = document.getElementById('snake-trail-overlay'); if (overlay) { const ctx = overlay.getContext('2d'); ctx.clearRect(0, 0, overlay.width, overlay.height); overlay.style.display = 'none'; // <--- Hide the overlay when trail is off } } // === INITIAL MENU VISIBILITY === menu.style.display = state.menuVisible ? 'block' : 'none'; // === INITIAL FPS DISPLAY === fpsDisplay.style.display = state.features.fpsDisplay ? 'block' : 'none'; // === INITIAL PING DISPLAY === pingDisplay.style.display = 'block'; // === INITIAL CIRCLE VISUAL COLOR === circleVisual.style.border = `2px dashed ${hexToRgba(state.menuColor, 0.7)}`; function snakeTrailAnimationLoop() { requestAnimationFrame(snakeTrailAnimationLoop); drawSnakeTrail(); } snakeTrailAnimationLoop(); setInterval(() => { if (!state.features.snakeTrail) { state.snakeTrailPoints = []; return; } // Get mouse screen position const mouseX = realMouseX; const mouseY = realMouseY; // Convert screen position to world (game) coordinates const viewX = window.snake ? window.snake.xx || 0 : 0; const viewY = window.snake ? window.snake.yy || 0 : 0; const viewZoom = window.gsc || 1; const screenCenterX = window.innerWidth / 2; const screenCenterY = window.innerHeight / 2; // This formula converts screen (mouse) to world coordinates const worldX = viewX + (mouseX - screenCenterX) / viewZoom; const worldY = viewY + (mouseY - screenCenterY) / viewZoom; if ( state.snakeTrailPoints.length === 0 || Math.abs(state.snakeTrailPoints[state.snakeTrailPoints.length-1].x - worldX) > 1 || Math.abs(state.snakeTrailPoints[state.snakeTrailPoints.length-1].y - worldY) > 1 ) { state.snakeTrailPoints.push({ x: worldX, y: worldY, time: Date.now() }); // Limit trail length if (state.snakeTrailPoints.length > 100) state.snakeTrailPoints.shift(); } }, 30); document.addEventListener('click', async function(e) { const chatUserSpan = e.target.closest('.chat-username'); if (!chatUserSpan) { document.getElementById('profile-popup')?.remove(); return; } document.getElementById('profile-popup')?.remove(); const uid = chatUserSpan.dataset.uid; const username = chatUserSpan.textContent; // Get menu color from state const menuColor = state.menuColor || '#4CAF50'; // Fetch online status from Firebase let isOnline = false; try { const onlineSnap = await firebase.database().ref("onlineUsers/" + uid).once('value'); isOnline = !!(onlineSnap.exists() && onlineSnap.val() && onlineSnap.val().name); } catch { isOnline = false; } // Optionally, use a default avatar or generate one: const avatarUrl = `https://api.dicebear.com/7.x/identicon/svg?seed=${encodeURIComponent(uid)}`; // Build the popup const popup = document.createElement('div'); popup.className = 'profile-popup'; popup.id = 'profile-popup'; popup.style.setProperty('--menu-color', menuColor); popup.innerHTML = ` <button class="close-btn" title="Close">×</button> <img class="avatar" src="${avatarUrl}" alt="Avatar"> <div style="font-size:1.23em;font-weight:bold;margin-bottom:2px;">${username}</div> <div style="margin-bottom:10px;"> <span class="status-dot" style="background:${isOnline ? '#0f0' : '#888'}"></span> <span style="font-size:1.04em;">${isOnline ? 'Online' : 'Offline'}</span> </div> <div style="font-size:0.97em;color:#fff;opacity:0.87;margin-bottom:8px;"> <b>UID:</b> <span style="font-family:monospace;">${uid}</span> </div> `; document.body.appendChild(popup); // Close logic popup.querySelector('.close-btn').onclick = () => popup.remove(); popup.onclick = e => e.stopPropagation(); // Prevent click-through // Remove popup on outside click or ESC setTimeout(() => { document.addEventListener('click', function handler(ev) { if (!popup.contains(ev.target)) { popup.remove(); document.removeEventListener('click', handler, true); } }, true); document.addEventListener('keydown', function handler(ev) { if (ev.key === "Escape") { popup.remove(); document.removeEventListener('keydown', handler, true); } }, true); }, 0); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址