您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Ultimate Slither.io Mod Menu with Chat & Custom UI - Fixed chat toggle and simplify
当前为
// ==UserScript== // @name DSC.GG/143X VX - SLITHER.IO MOD MENU W/ CHAT // @namespace http://tampermonkey.net/ // @version v13 // @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'; // === CONFIG === const config = { menuPosition: 'right', defaultCircleRadius: 150, circleRadiusStep: 20, minCircleRadius: 50, maxCircleRadius: 300, deathSoundURL: 'https://actions.google.com/sounds/v1/alarms/beep_short.ogg', 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', deathSound: 'v', showServer: 't', chatEnabled: 'enter', // Enter key: focus chat input (disable keybinds) zoomIn: 'z', zoomOut: 'x', zoomReset: 'c', screenshot: 'p', github: 'g', discord: 'd', godMode: 'y' }, features: { circleRestriction: false, autoCircle: false, performanceMode: 1, deathSound: true, snakeTrail: false, snakeTrailColor: '#FFD700', fpsDisplay: false, autoBoost: false, showServer: 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: '', leaderboard: [], 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 }; // state variables let chatHistory = []; let autoCircleRAF = null; // 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); // === 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 = 'Type message... (Enter to send)'; 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); } // Add this function to update the online users list // 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" }); // === 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="...">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; } // Chat message listener // Find this part in your Firebase chat message listener firebase.database().ref("slitherChat") .orderByChild("time") .limitToLast(50) .on("child_added", async (snapshot) => { const msg = snapshot.val(); 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'; // Always rainbow for dxxthly owner UID let nameHtml; if ( (msg.uid === "CiOpgh1RLBg3l5oXn0SAho66Po93" && msg.name && msg.name.toLowerCase() === "dxxthly") || (msg.uid === "P75eMwh756Rb6h1W6iqQfHN2Dm92" && msg.name && msg.name.toLowerCase() === "wayne") ) { // Always rainbow for dxxthly owner or Wayne admin UID nameHtml = rainbowTextStyle(msg.name || 'Anon'); } else if (msg.uid) { const roleSnap = await firebase.database().ref(`roles/${msg.uid}`).once('value'); if (roleSnap.exists()) { nameHtml = rainbowTextStyle(msg.name || 'Anon'); } else { const userColor = msg.chatNameColor || '#FFD700'; nameHtml = `<span style="color:${userColor}">${msg.name}</span>`; } } else { nameHtml = `<span style="color:#FFD700">${msg.name}</span>`; } // === ADD THIS BLOCK HERE === // Store formatted message in history array const formattedMsg = `<span style="background:rgba(40,40,40,0.93);padding:6px 10px;border-radius:7px;display:block;color:#fff;"><b>${nameHtml}:</b> ${msg.text}</span>`; chatHistory.push(formattedMsg); if (chatHistory.length > 50) chatHistory.shift(); // Render entire chat history const chatBody = document.getElementById('mod-menu-chat-body'); if (chatBody) { chatBody.innerHTML = chatHistory.map(msg => `<div style="margin-bottom:5px;word-break:break-word">${msg}</div>` ).join(''); chatBody.scrollTop = chatBody.scrollHeight; } // === END NEW BLOCK === }); // 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) 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; 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 ? '▼' : '▶'; 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">vX</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; "> <div><b>Zoom:</b> ${Math.round(100 / state.zoomFactor)}%</div> <div> <button id="test-ping-btn-simple" 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-simple" style="margin-left:8px;color:#FFD700;"></span> </div> <div><b>FPS:</b> ${state.fps}</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> <div style="text-align:center;font-size:12px;color:#aaa;border-top:1px solid #444;padding-top:10px"> Press <strong>${state.keybinds.toggleMenu.toUpperCase()}</strong> to hide/show menu | <b>DSC.GG/143X</b> | <strong>${state.keybinds.screenshot.toUpperCase()}</strong> Screenshot<br> <span style="color:#aaa;">Made by: <b>dxxthly.</b> & <b>waynesg</b> on Discord</span> </div> </div> `; 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'; 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">vX</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> `; // --- Keybind Updates in Menu --- 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> </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:#4CAF50;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;"> <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> </div> </div> <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:#4CAF50;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:#4CAF50;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 trailToggleBtn = document.getElementById('trail-toggle-btn'); if (trailToggleBtn) { trailToggleBtn.onclick = () => { state.features.snakeTrail = !state.features.snakeTrail; if (!state.features.snakeTrail) state.snakeTrailPoints = []; // Clear on off 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(() => { const simplifyBtn = document.getElementById('simplify-menu-btn'); if (simplifyBtn) { simplifyBtn.onclick = () => { state.simplified = true; sessionStorage.setItem('modMenuSimplified', 'true'); updateMenu(); }; } const toggle = document.getElementById('customization-toggle'); if (toggle) { toggle.onclick = () => { state.showCustomization = !state.showCustomization; sessionStorage.setItem('showCustomization', state.showCustomization); 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); 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); } (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); }, 100); }; function closeModal() { overlay.style.display = 'none'; document.removeEventListener('keydown', keyListener, true); pendingAction = null; } function keyListener(e) { if (!pendingAction) return; e.preventDefault(); 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 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.</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 === 'enter' && 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: state.zoomFactor = Math.max(0.1, state.zoomFactor - 0.1); updateMenu(); break; case binds.zoomOut: state.zoomFactor = Math.min(2, state.zoomFactor + 0.1); updateMenu(); break; case binds.zoomReset: state.zoomFactor = 1.0; 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 '1': state.features.performanceMode = 1; applyPerformanceMode(); break; case '2': state.features.performanceMode = 2; applyPerformanceMode(); break; case '3': state.features.performanceMode = 3; applyPerformanceMode(); break; } }); // === 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 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 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 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); } } // === SERVER & LEADERBOARD UPDATES === function updateServerAndLeaderboard() { if (window.bso && window.bso && window.bso.ip) { state.server = window.bso.ip; } if (window.lb && Array.isArray(window.lb)) { state.leaderboard = window.lb.map(x => x ? (x.nk || x.name || 'Unknown') : 'Unknown'); } setTimeout(updateServerAndLeaderboard, 1000); } updateServerAndLeaderboard(); // === 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; } // Track actual mouse position (this always works!) const mouseX = window.xm !== undefined ? window.xm : window.mouseX || 0; const mouseY = window.ym !== undefined ? window.ym : window.mouseY || 0; // Only add points when the mouse moves if (state.snakeTrailPoints.length === 0 || Math.abs(state.snakeTrailPoints[state.snakeTrailPoints.length-1].x - mouseX) > 5 || Math.abs(state.snakeTrailPoints[state.snakeTrailPoints.length-1].y - mouseY) > 5) { state.snakeTrailPoints.push({ x: mouseX || window.innerWidth/2, y: mouseY || window.innerHeight/2, time: Date.now() }); // Limit trail length if (state.snakeTrailPoints.length > 100) state.snakeTrailPoints.shift(); } }, 30); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址