// ==UserScript==
// @name SLITHER.IO MOD MENU W/ CHAT - DSC.GG/143X VX
// @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);
})();