// ==UserScript== // @name SigMod Client (Macros) // @version 10.2.0.4 // @description The best mod you can find for Sigmally - Agar.io: Macros, Friends, tag system (minimap, chat), color mod, custom skins, AutoRespawn, save names, themes and more! // @author Cursed // @match https://*.sigmally.com/* // @icon https://czrsd.com/static/sigmod/SigMod25-rounded.png // @run-at document-end // @license MIT // @grant none // @namespace https://greasyfork.org/users/981958 // @downloadURL none // ==/UserScript== (function () { 'use strict'; const version = 10; const serverVersion = '4.0.3'; const storageVersion = 1.0; const storageName = 'SigModClient-settings'; const headerAnim = 'https://czrsd.com/static/sigmod/sigmodclient.gif'; const libs = { chart: 'https://cdn.jsdelivr.net/npm/chart.js', colorPicker: { js: 'https://unpkg.com/alwan/dist/js/alwan.min.js', css: 'https://unpkg.com/alwan/dist/css/alwan.min.css', }, jszip: 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js', }; const defaultSettings = { storageVersion, macros: { feedSpeed: 40, keys: { rapidFeed: 'w', respawn: 'b', location: 'y', saveImage: null, splits: { double: 'd', triple: 'f', quad: 'g', doubleTrick: null, selfTrick: null, }, line: { horizontal: 's', vertical: 't', fixed: null, instantSplit: 0, }, toggle: { menu: 'v', chat: 'z', names: null, skins: null, autoRespawn: null, }, }, mouse: { left: null, right: null, }, }, game: { font: 'Ubuntu', borderColor: null, foodColor: null, cellColor: null, virusImage: '/assets/images/viruses/2.png', shortenNames: false, removeOutlines: false, skins: { original: null, replacement: null, }, map: { color: null, image: '', }, name: { color: null, gradient: { enabled: false, left: null, right: null, }, }, }, themes: { current: 'Dark', custom: [], inputBorderRadius: null, menuBorderRadius: null, inputBorder: '1px', hideDiscordBtns: false, hideLangs: false, }, settings: { tag: null, savedNames: [], autoRespawn: false, playTimer: false, mouseTracker: false, autoClaimCoins: false, showChallenges: false, deathScreenPos: 'center', removeShopPopup: false, }, chat: { bgColor: '#00000040', textColor: '#ffffff', compact: false, themeColor: '#8a25e5', showTime: true, showNameColors: true, showClientChat: false, showChatButtons: true, blurTag: false, locationText: '{pos}', }, modAccount: { authorized: false, }, }; let modSettings; const stored = localStorage.getItem(storageName); try { modSettings = stored ? JSON.parse(stored) : defaultSettings; } catch (e) { modSettings = defaultSettings; } // really rare cases, but in case the storage structure changes completely again if ( !modSettings.storageVersion || modSettings.storageVersion !== storageVersion ) { localStorage.removeItem(storageName); location.reload(); } if (!stored) updateStorage(); // intercept fetches let fetchedUser = 0; const originalFetch = window.fetch; window.fetch = new Proxy(originalFetch, { async apply(target, thisArg, argumentsList) { const [url] = argumentsList; const response = await target.apply(thisArg, argumentsList); if (typeof url === 'string') { if (url.includes('/server/auth')) { const data = await response.clone().json(); if (data) mods.handleGoogleAuth(data.body?.user); } } return response; }, }); // for development let isDev = false; let port = 3001; // global sigmod window.sigmod = { version, server_version: serverVersion, storageName, settings: modSettings, }; // Global gameSettings object to store the Sigmally WebSocket, User instance and playing status /* * @typedef {Object} User * @property {string} _id * @property {number} boost * @property {Object.} cards * @property {string} clan * @property {string} createTime * @property {string} email * @property {number} exp * @property {string} fullName * @property {string} givenName * @property {number} gold * @property {string} googleID * @property {number} hourlyTime * @property {string} imageURL * @property {any[]} lastSkinUsed * @property {number} level * @property {number} nextLevel * @property {number} progress * @property {number} seasonExp * @property {Object.} sigma * @property {string[]} skins * @property {number} subscription * @property {string} updateTime * @property {string} token * * @property {WebSocket} ws * @property {User} user * @property {boolean} isPlaying */ window.gameSettings = { ws: null, user: null, isPlaying: false, }; // --------- HELPER FUNCTIONS --------- \\ // --- General // --- Colors // --- Game // --- Time // --- (Coordinates) function updateStorage() { localStorage.setItem(storageName, JSON.stringify(modSettings)); } const byId = (id) => document.getElementById(id); const debounce = (func, delay) => { let timeoutId; return function (...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; } const wait = async (ms) => { return new Promise((r) => setTimeout(r, ms)); } const noXSS = (text) => { return text .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } // generate random string const rdmString = (length) => { return [...Array(length)] .map(() => Math.random().toString(36).charAt(2)) .join(''); }; const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); // --------- Colors --------- // // rgba values to hex color code const RgbaToHex = (code) => { const rgbaValues = code.match(/\d+/g); const [r, g, b] = rgbaValues.slice(0, 3); return `#${Number(r).toString(16).padStart(2, '0')}${Number(g) .toString(16) .padStart(2, '0')}${Number(b).toString(16).padStart(2, '0')}`; } const bytesToHex = (r, g, b) => { return ( '#' + ((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1) ); } // --------- Game --------- // const menuClosed = () => { const menuWrapper = byId('menu-wrapper'); return menuWrapper.style.display === 'none'; } const isDead = () => { const __line2 = byId('__line2'); return !__line2.classList.contains('line--hidden'); } const getGameMode = () => { const gameMode = byId('gamemode'); if (!gameMode.value) { return 'Tourney'; } const options = Object.values(gameMode.querySelectorAll('option')); const selectedOption = options.filter( (option) => option.value === gameMode.value )[0]; return selectedOption.textContent.split(' ')[0]; } function keypress(key, keycode) { const keyDownEvent = new KeyboardEvent('keydown', { key: key, code: keycode, }); const keyUpEvent = new KeyboardEvent('keyup', { key: key, code: keycode, }); window.dispatchEvent(keyDownEvent); window.dispatchEvent(keyUpEvent); } function mousemove(sx, sy) { const mouseMoveEvent = new MouseEvent('mousemove', { clientX: sx, clientY: sy, }); const canvas = byId('canvas'); canvas.dispatchEvent(mouseMoveEvent); } const getCoordinates = (border, gridCount = 5) => { const { left, top, width, height } = border; const gridSize = width / gridCount; const coordinates = {}; for (let i = 0; i < gridCount; i++) { for (let j = 0; j < gridCount; j++) { const label = String.fromCharCode(65 + i) + (j + 1); coordinates[label] = { min: { x: left + i * gridSize, y: top + j * gridSize }, max: { x: left + (i + 1) * gridSize, y: top + (j + 1) * gridSize }, }; } } return coordinates; }; // time formatters const prettyTime = { fullDate: (dateTimestamp, time = false) => { const date = new Date(dateTimestamp); const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); const year = date.getFullYear(); const formattedDate = `${day}.${month}.${year}`; if (time) { const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${hours}:${minutes}:${seconds} ${formattedDate}`; } return formattedDate; }, am_pm: (date) => { if (!date) return ''; const d = new Date(date); const hours = d.getHours(); const minutes = String(d.getMinutes()).padStart(2, '0'); const ampm = hours >= 12 ? 'PM' : 'AM'; const formattedHours = (hours % 12 || 12) .toString() .padStart(2, '0'); return `${formattedHours}:${minutes} ${ampm}`; }, time_ago: (timestamp, isIso = false) => { if (!timestamp) return ''; const currentTime = new Date(); const elapsedTime = isIso ? currentTime - new Date(timestamp) : currentTime - timestamp; const seconds = Math.floor(elapsedTime / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); const years = Math.floor(days / 365); if (years > 0) { return years === 1 ? '1 year ago' : `${years} years ago`; } else if (days > 0) { return days === 1 ? '1 day ago' : `${days} days ago`; } else if (hours > 0) { return hours === 1 ? '1 hour ago' : `${hours} hours ago`; } else if (minutes > 0) { return minutes === 1 ? '1 minute ago' : `${minutes} minutes ago`; } else { return seconds <= 1 ? '1s>' : `${seconds}s ago`; } }, getTimeLeft(timestamp) { let totalSeconds = Math.max(0, Math.floor((timestamp - Date.now()) / 1000)); const timeUnits = [['d', 86400], ['h', 3600], ['m', 60], ['s', 1]]; let result = ''; for (const [unit, seconds] of timeUnits) { if (totalSeconds >= seconds) { const value = Math.floor(totalSeconds / seconds); totalSeconds %= seconds; result += `${value}${unit}`; } } return result || '0s'; } }; const getStringUTF8 = (view, o) => { const startOffset = o; while (view.getUint8(o) !== 0 && o < view.byteLength) { o++; } return o > startOffset ? [new TextDecoder().decode(new DataView(view.buffer, startOffset, o - startOffset)), o + 1] : ['', o + 1]; }; // --------- END HELPER FUNCTIONS --------- // let client = null; let freezepos = false; // --------- Sigmally WebSocket Handler --------- // class SigWsHandler { constructor() { this.handshake = false; this.C = new Uint8Array(256); this.R = new Uint8Array(256); this.overrideWebSocketSend(); } overrideWebSocketSend() { const handler = this; window.WebSocket = new Proxy(window.WebSocket, { construct(target, args) { const wsInstance = new target(...args); if (args[0].includes('sigmally.com')) { handler.setupWebSocket(wsInstance); } return wsInstance; }, }); } setupWebSocket(ws) { window.gameSettings.ws = ws; // if 'save' is in localstorage, it indicates that you are logged in to Google; if that is the case, it // will load the client after the authorization to load the modClient correctly. // This loads the client instantly if you're not logged in to Google if (!localStorage.getItem('save') && !client) { client = new modClient(); } ws.addEventListener('close', () => this.handleWebSocketClose()); ws.sendPacket = this.sendPacket.bind(this); window.sendPlay = this.sendPlay.bind(this); window.sendChat = this.sendChat.bind(this); window.sendMouseMove = this.sendMouseMove.bind(this); const originalSend = ws.send.bind(ws); ws.send = (data) => { try { const arrayBuffer = data instanceof ArrayBuffer ? data : data.buffer; const view = new DataView(arrayBuffer); const r = view.getUint8(); if (this.R[r] === 0) { window.gameSettings.isPlaying = true; } if (!window.sigfix && freezepos && this.R[r] === 16) { return; } originalSend(data); } catch (e) { console.error(e); } }; ws.addEventListener('message', (event) => this.handleMessage(event) ); } handleWebSocketClose() { this.handshake = false; window.gameSettings.isPlaying = false; playerPosition.x = null; playerPosition.y = null; byId('mod-messages').innerHTML = ''; setTimeout(mods.showOverlays, 500); } sendPacket(packet) { if (!window.gameSettings.ws) { console.error('WebSocket is not defined.'); return; } window.gameSettings.ws.send(packet); } sendPlay(playData) { const { sigfix } = window; if (sigfix) { sigfix.net.play(sigfix.world.selected, JSON.parse(playData)); return; } const json = JSON.stringify(playData); const encoded = textEncoder.encode(json); const buf = new Uint8Array(encoded.length + 2); buf[0] = this.C[0x00]; buf.set(encoded, 1); this.sendPacket(buf); } sendChat(text) { if (mods.aboveRespawnLimit && text === mods.respawnCommand) return; if (window.sigfix) return window.sigfix.net.chat(text); const encoded = textEncoder.encode(text); const buf = new Uint8Array(encoded.byteLength + 3); buf[0] = this.C[0x63]; buf.set(encoded, 2); this.sendPacket(buf); } sendMouseMove(x, y) { const {sigfix} = window; if (sigfix) { sigfix.net.move(sigfix.world.selected, x, y); return; } const view = new DataView(new ArrayBuffer(14)); view.setUint8(0, this.C[0x10]); view.setInt32(1, x, true); view.setInt32(5, y, true); this.sendPacket(view); } removePlayer(id) { const index = this.cells.players.indexOf(id); if (index !== -1) { this.cells.players.splice(index, 1); } } handleMessage(event) { const view = new DataView(event.data); let o = 0; if (!this.handshake) { this.performHandshake(view, o); return; } const r = view.getUint8(o++); switch (this.R[r]) { case 0x63: this.handleChatMessage(view, o); break; case 0x40: this.updateBorder(view, o); break; } } performHandshake(view, o) { let bytes = []; let b; while ((b = view.getUint8(o++)) !== 0) bytes.push(b); this.C.set(new Uint8Array(view.buffer.slice(o, o + 256))); o += 256; for (const i in this.C) { this.R[this.C[i]] = ~~i; } this.handshake = true; } handleChatMessage(view, o) { const flags = view.getUint8(o++); const rgb = Array.from({ length: 3 }, () => view.getUint8(o++) / 255); const hex = `#${rgb.map(c => Math.floor(c * 255).toString(16).padStart(2, '0')).join('')}`; let [name, message] = []; [name, o] = getStringUTF8(view, o); [message, o] = getStringUTF8(view, o); if (name.trim() === '') name = 'Unnamed'; if (!mods.mutedUsers.includes(name) && !mods.spamMessage(name, message) && !modSettings.chat.showClientChat) { mods.updateChat({ color: modSettings.chat.showNameColors ? hex : '#fafafa', name, message, time: modSettings.chat.showTime ? Date.now() : null, }); } } updateBorder(view, o) { const [left, top, right, bottom] = [ view.getFloat64(o, true), view.getFloat64(o + 8, true), view.getFloat64(o + 16, true), view.getFloat64(o + 24, true) ]; mods.border = { left, top, right, bottom, width: right - left, height: bottom - top } } } class SigFixHandler { constructor() { this.lastHadCells = false; this.checkInterval = null; this.updatePosInterval = null; this.sendPosInterval = null; this.init(); } overrideMoveFunction() { if (!window.sigfix?.net?.move) return; const originalMove = window.sigfix.net.move; let isHandlingFreeze = false; window.sigfix.net.move = (...args) => { if (freezepos && !isHandlingFreeze) { isHandlingFreeze = true; originalMove.call(this, playerPosition.x, playerPosition.y); isHandlingFreeze = false; return; } return originalMove.apply(this, args); }; } calculatePlayerPosition() { let ownX = 0, ownY = 0, ownN = 0; const ownedCells = window.sigfix.world.views.get(window.sigfix.world.selected)?.owned || []; ownedCells.forEach(id => { const cell = window.sigfix.world.cells.get(id)?.merged; if (cell) { ownN++; ownX += cell.nx; ownY += cell.ny; } }); return ownN > 0 ? { x: ownX / ownN, y: ownY / ownN } : null; } updatePlayerPos() { const newPos = this.calculatePlayerPosition(); if (newPos) { playerPosition.x = newPos.x; playerPosition.y = newPos.y; this.lastHadCells = true; } else if (this.lastHadCells) { playerPosition.x = null; playerPosition.y = null; this.sendPlayerPos(); this.lastHadCells = false; } } sendPlayerPos() { if (playerPosition.x !== null && playerPosition.y !== null && client?.ws?.readyState === 1 && modSettings.settings.tag) { client.send({ type: 'position', content: { x: playerPosition.x, y: playerPosition.y } }); } } startIntervals() { this.updatePosInterval = setInterval(this.updatePlayerPos.bind(this)); this.sendPosInterval = setInterval(this.sendPlayerPos.bind(this), 300); } checkSigFix() { if (window.sigfix) { this.startIntervals(); this.overrideMoveFunction(); clearInterval(this.checkInterval); } } init() { const checkSigFix = () => { this.checkSigFix(); if (window.sigfix?.net) { setTimeout(() => { this.checkInterval = null; }, 1000); } }; this.checkInterval = setInterval(checkSigFix, 100); } } new SigFixHandler(); // --------- Mod Client --------- // class modClient { constructor() { this.ws = null; this.wsUrl = isDev ? `ws://localhost:${port}/ws` : 'wss://mod.czrsd.com/ws'; this.retries = 0; this.maxRetries = 4; this.updateAvailable = false; this.id = null; this.connectedAmount = 0; this.connect(); } connect() { this.ws = new WebSocket(this.wsUrl); this.ws.binaryType = 'arraybuffer'; window.sigmod.ws = this.ws; this.ws.addEventListener('open', this.onOpen.bind(this)); this.ws.addEventListener('close', this.onClose.bind(this)); this.ws.addEventListener('message', this.onMessage.bind(this)); this.ws.addEventListener('error', this.onError.bind(this)); } async onOpen() { this.connectedAmount++; await this.waitForDOMLoad(); this.updateClientInfo(); this.updateTagInfo(); // Send nick if client got disconnected more than one time if (this.connectedAmount > 1) { client.send({ type: 'update-nick', content: mods.nick, }); } } waitForDOMLoad() { return new Promise((resolve) => setTimeout(resolve, 500)); } updateClientInfo() { this.send({ type: 'server-changed', content: getGameMode(), }); this.send({ type: 'version', content: serverVersion, }); } updateTagInfo() { const tagElement = document.querySelector('#tag'); const tagText = document.querySelector('.tagText'); const tagValue = this.getTagFromUrl(); if (tagValue) { modSettings.settings.tag = tagValue; updateStorage(); tagElement.value = tagValue; tagText.innerText = `Tag: ${tagValue}`; this.send({ type: 'update-tag', content: modSettings.settings.tag, }); } else if (modSettings.settings.tag) { tagElement.value = modSettings.settings.tag; tagText.innerText = `Tag: ${modSettings.settings.tag}`; this.send({ type: 'update-tag', content: modSettings.settings.tag, }); } } getTagFromUrl() { const urlParams = new URLSearchParams(window.location.search); const tagValue = urlParams.get('tag'); return tagValue ? tagValue.replace(/\/$/, '') : null; } onClose() { if (this.updateAvailable) return; this.retries++; if (this.retries > this.maxRetries) throw new Error('SigMod server down.'); setTimeout(() => this.connect(), 2000); // auto reconnect with delay } onMessage(event) { const message = this.parseMessage(event.data); if (!message || !message.type) return; switch (message.type) { case 'sid': this.handleSidMessage(message.content); break; case 'ping': this.handlePingMessage(); break; case 'minimap-data': mods.updData(message.content); break; case 'chat-message': this.handleChatMessage(message.content); break; case 'private-message': mods.updatePrivateChat(message.content); break; case 'update-available': this.handleUpdateAvailable(message.content); break; case 'alert': mods.handleAlert(message.content); break; case 'tournament-preview': mods.tData = message.content; mods.showTournament(message.content); break; case 'tournament-message': mods.updateChat({ name: '[TOURNAMENT]', message: message.content, time: modSettings.chat.showTime ? Date.now() : null, }); break; case 'tournament-session': mods.tournamentSession(message.content); break; case 'get-score': mods.getScore(message.content); break; case 'round-end': mods.roundEnd(message.content); break; case 'round-ready': mods.tournamentReady(message.content); break; case 'tournament-data': mods.handleTournamentData(message.content); break; case 'error': mods.modAlert(message.content.message, 'danger'); break; default: console.error('Unknown message type:', message.type); } } onError(event) { console.error('WebSocket error:', event); } send(data) { if (!data || this.ws.readyState !== 1) return; const binaryData = textEncoder.encode(JSON.stringify(data)); this.ws.send(binaryData); } parseMessage(data) { try { const stringData = textDecoder.decode( new Uint8Array(data) ); return JSON.parse(stringData); } catch (error) { console.error('Failed to parse message:', error); return null; } } handleSidMessage(content) { this.id = content; if (!modSettings.modAccount.authorized) return; setTimeout(() => { mods.auth(content); }, 1000); } handlePingMessage() { mods.ping.latency = Date.now() - mods.ping.start; mods.ping.end = Date.now(); byId('clientPing').innerHTML = `Client Ping: ${mods.ping.latency}ms`; } handleChatMessage(content) { if (!content) return; let { admin, mod, vip, name, message, color } = content; name = this.formatChatName(admin, mod, vip, name); mods.updateChat({ admin, mod, color, name, message, time: modSettings.chat.showTime ? Date.now() : null, }); } formatChatName(admin, mod, vip, name) { if (admin) name = '[Owner] ' + name; if (mod) name = '[Mod] ' + name; if (vip) name = '[VIP] ' + name; if (!name) name = 'Unnamed'; return name; } handleUpdateAvailable(content) { byId('play-btn').setAttribute('disabled', 'disabled'); byId('spectate-btn').setAttribute('disabled', 'disabled'); this.updateAvailable = true; this.createModAlert(content); } createModAlert(content) { const modAlert = document.createElement('div'); modAlert.classList.add('modAlert'); modAlert.innerHTML = ` You are using an old mod version. Please update.
`; document.body.append(modAlert); } } function randomPos() { let eventOptions = { clientX: Math.floor(Math.random() * window.innerWidth), clientY: Math.floor(Math.random() * window.innerHeight), bubbles: true, cancelable: true, }; let event = new MouseEvent('mousemove', eventOptions); document.dispatchEvent(event); } let moveInterval = setInterval(randomPos); setTimeout(() => clearInterval(moveInterval), 600); // Disable chat before game settings actually load localStorage.setItem('settings', JSON.stringify({ ...JSON.parse(localStorage.getItem('settings')), showChat: false })); function addSettings() { const gameSettings = document.querySelector('.checkbox-grid'); if (!gameSettings) return; const div = document.createElement('div'); div.innerHTML = ` `; while (div.children.length > 0) gameSettings.append(div.children[0]); } addSettings(); async function checkDiscordLogin() { // popup window from discord login const urlParams = new URLSearchParams(window.location.search); let accessToken = urlParams.get('access_token'); let refreshToken = urlParams.get('refresh_token'); if (!accessToken || !refreshToken) return; const url = isDev ? `http://localhost:${port}/discord/login/` : 'https://mod.czrsd.com/discord/login/'; const overlay = document.createElement('div'); overlay.style = `position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: #050505; display: flex; justify-content: center; align-items: center; z-index: 999999`; overlay.innerHTML = ` Login... `; document.body.append(overlay); setTimeout(async () => { if (refreshToken.endsWith('/')) { refreshToken = refreshToken.substring( 0, refreshToken.length - 1 ); } else { return; } await fetch( `${url}?accessToken=${accessToken}&refreshToken=${refreshToken}`, { method: 'GET', credentials: 'include', } ); modSettings.modAccount.authorized = true; updateStorage(); window.close(); }, 500); } checkDiscordLogin(); let mods = {}; let playerPosition = { x: null, y: null }; let lastGetScore = 0; let lastPosTime = 0; let dead = false; let dead2 = false; function Mod() { this.nick = null; this.profile = {}; this.friends_settings = window.sigmod.friends_settings = {}; this.friend_names = window.sigmod.friend_names = new Set(); this.splitKey = { keyCode: 32, code: 'Space', cancelable: true, composed: true, isTrusted: true, which: 32, }; this.ping = { latency: NaN, intervalId: null, start: null, end: null, }; this.dayTimer = null; this.challenges = []; this.scrolling = false; this.onContext = false; this.mouseDown = false; this.mouseX = 0; this.mouseY = 0; this.renderedMessages = 0; this.maxChatMessages = 200; this.mutedUsers = []; this.blockedChatData = { names: [], messages: [] }; this.respawnCommand = '/leaveworld'; this.aboveRespawnLimit = false; this.cellSize = 0; this.miniMapData = []; this.border = {}; this.dbCache = null; this.tourneyPassword = ''; this.lastOneStanding = false; this.skins = []; this.virusImageLoaded = false; this.skinImageLoaded = false; this.routes = { discord: { auth: isDev ? 'https://discord.com/oauth2/authorize?client_id=1067097357780516874&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3001%2Fdiscord%2Fcallback&scope=identify' : 'https://discord.com/oauth2/authorize?client_id=1067097357780516874&response_type=code&redirect_uri=https%3A%2F%2Fmod.czrsd.com%2Fdiscord%2Fcallback&scope=identify', }, }; // for SigMod specific this.appRoutes = { badge: isDev ? `http://localhost:${port}/badge` : 'https://mod.czrsd.com/badge', signIn: (path) => isDev ? `http://localhost:${port}/${path}` : `https://mod.czrsd.com/${path}`, auth: isDev ? `http://localhost:${port}/auth` : `https://mod.czrsd.com/auth`, users: isDev ? `http://localhost:${port}/users` : `https://mod.czrsd.com/users`, search: isDev ? `http://localhost:${port}/search` : `https://mod.czrsd.com/search`, request: isDev ? `http://localhost:${port}/request` : `https://mod.czrsd.com/request`, friends: isDev ? `http://localhost:${port}/me/friends` : `https://mod.czrsd.com/me/friends`, profile: (id) => isDev ? `http://localhost:${port}/profile/${id}` : `https://mod.czrsd.com/profile/${id}`, myRequests: isDev ? `http://localhost:${port}/me/requests` : `https://mod.czrsd.com/me/requests`, handleRequest: isDev ? `http://localhost:${port}/me/handle` : `https://mod.czrsd.com/me/handle`, logout: isDev ? `http://localhost:${port}/logout` : `https://mod.czrsd.com/logout`, imgUpload: isDev ? `http://localhost:${port}/me/upload` : `https://mod.czrsd.com/me/upload`, removeAvatar: isDev ? `http://localhost:${port}/me/handle` : `https://mod.czrsd.com/me/handle`, editProfile: isDev ? `http://localhost:${port}/me/edit` : `https://mod.czrsd.com/me/edit`, delProfile: isDev ? `http://localhost:${port}/me/remove` : `https://mod.czrsd.com/me/remove`, updateSettings: isDev ? `http://localhost:${port}/me/update-settings` : `https://mod.czrsd.com/me/update-settings`, chatHistory: (id) => isDev ? `http://localhost:${port}/me/chat/${id}` : `https://mod.czrsd.com/me/chat/${id}`, onlineUsers: isDev ? `http://localhost:${port}/onlineUsers` : `https://mod.czrsd.com/onlineUsers`, announcements: isDev ? `http://localhost:${port}/announcements` : `https://mod.czrsd.com/announcements`, announcement: (id) => isDev ? `http://localhost:${port}/announcement/${id}` : `https://mod.czrsd.com/announcement/${id}`, fonts: isDev ? `http://localhost:${port}/fonts` : 'https://mod.czrsd.com/fonts', blockedChatData: 'https://mod.czrsd.com/spam.json', }; this.init(); } Mod.prototype = { get style() { return ` @import url('https://fonts.googleapis.com/css2?family=Titillium+Web:wght@400;600;700&display=swap'); .mod_menu { position: absolute; top: 0; left: 0; width: 100%; height: 100vh; background: rgba(0, 0, 0, .6); z-index: 999999; display: flex; justify-content: center; align-items: center; color: #fff; transition: all .3s ease; } .mod_menu * { margin: 0; padding: 0; font-family: 'Ubuntu'; box-sizing: border-box; } .mod_menu_wrapper { position: relative; display: flex; flex-direction: column; width: 700px; height: 500px; background: #111; border-radius: 12px; overflow: hidden; box-shadow: 0 5px 10px #000; } .mod_menu_header { display: flex; width: 100%; position: relative; height: 60px; } .mod_menu_header .header_img { width: 100%; height: 60px; object-fit: cover; object-position: center; position: absolute; } .mod_menu_header button { display: flex; justify-content: center; align-items: center; position: absolute; right: 10px; top: 30px; background: rgba(11, 11, 11, .7); width: 42px; height: 42px; font-size: 16px; transform: translateY(-50%); } .mod_menu_header button:hover { background: rgba(11, 11, 11, .5); } .mod_menu_inner { display: flex; } .mod_menu_navbar { display: flex; flex-direction: column; gap: 10px; min-width: 132px; padding: 10px; background: #181818; height: 440px; } .mod_nav_btn, .modButton-black { display: flex; justify-content: space-evenly; align-items: center; padding: 5px; background: #050505; border-radius: 8px; font-size: 16px; border: 1px solid transparent; outline: none; width: 100%; transition: all .3s ease; } label.modButton-black { font-weight: 400; cursor: pointer; } .modButton-black[disabled] { background: #333; cursor: default; } .mod_selected { border: 1px solid rgba(89, 89, 89, .9); } .mod_nav_btn img { width: 22px; } .mod_menu_content { width: 100%; padding: 10px; position: relative; max-width: 568px; } .mod_tab { width: 100%; height: 100%; display: flex; flex-direction: column; gap: 5px; overflow-y: auto; overflow-x: hidden; max-height: 420px; opacity: 1; transition: all .2s ease; } .modColItems { display: flex; flex-direction: column; align-items: center; gap: 15px; width: 100%; } .modColItems_2 { display: flex; flex-direction: column; align-items: start; justify-content: start; background: #050505; gap: 8px; border-radius: 0.5rem; padding: 10px; width: 100%; } .modRowItems { display: flex; justify-content: center; align-items: center; background: #050505; gap: 58px; border-radius: 0.5rem; padding: 10px; width: 100%; } .form-control { border-width: 1px; } .form-control.error-border { border-color: #AC3D3D; } .modSlider { --thumb-color: #d9d9d9; -webkit-appearance: none; appearance: none; width: 100%; height: 8px; background: #333; border-radius: 5px; outline: none; transition: all 0.3s ease; } .modSlider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 20px; height: 20px; border-radius: 50%; background: var(--thumb-color); } .modSlider::-moz-range-thumb { width: 20px; height: 20px; border-radius: 50%; background: var(--thumb-color); } .modSlider::-ms-thumb { width: 20px; height: 20px; border-radius: 50%; background: var(--thumb-color); } input:focus, select:focus, button:focus{ outline: none; } .macros_wrapper { display: flex; width: 100%; justify-content: center; flex-direction: column; gap: 10px; background: #050505; padding: 10px; border-radius: 0.75rem; } .macrosContainer { display: flex; width: 100%; justify-content: center; align-items: center; gap: 20px; } .macroRow { background: #121212; border-radius: 5px; padding: 7px; display: flex; justify-content: space-between; align-items: center; gap: 10px; } .keybinding { border-radius: 5px; background: #242424; border: none; color: #fff; padding: 2px 5px; max-width: 50px; font-weight: 500; text-align: center; } .closeBtn{ width: 46px; background-color: transparent; display: flex; justify-content: center; align-items: center; } .select-btn { padding: 15px 20px; background: #222; border-radius: 2px; position: relative; } .select-btn:active { scale: 0.95 } .select-btn::before { content: "..."; font-size: 20px; color: #fff; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } .text { user-select: none; font-weight: 500; text-align: left; } .modButton { background-color: #252525; border-radius: 4px; color: #fff; transition: all .3s; outline: none; padding: 7px; font-size: 13px; border: none; } .modButton:hover { background-color: #222 } .modInput { background-color: #111; border: none; border-radius: 5px; position: relative; border-top-right-radius: 4px; border-top-left-radius: 4px; font-weight: 500; padding: 5px; color: #fff; } .modNumberInput:disabled { color: #777; } .modCheckbox input[type="checkbox"] { display: none; visibility: hidden; } .modCheckbox label { display: inline-block; } .modCheckbox .cbx { position: relative; top: 1px; width: 17px; height: 17px; margin: 2px; border: 1px solid #c8ccd4; border-radius: 3px; vertical-align: middle; transition: background 0.1s ease; cursor: pointer; } .modCheckbox .cbx:after { content: ''; position: absolute; top: 1px; left: 5px; width: 5px; height: 11px; opacity: 0; transform: rotate(45deg) scale(0); border-right: 2px solid #fff; border-bottom: 2px solid #fff; transition: all 0.3s ease; transition-delay: 0.15s; } .modCheckbox input[type="checkbox"]:checked ~ .cbx { border-color: transparent; background: #6871f1; box-shadow: 0 0 10px #2E2D80; } .modCheckbox input[type="checkbox"]:checked ~ .cbx:after { opacity: 1; transform: rotate(45deg) scale(1); } .SettingsButton{ border: none; outline: none; margin-right: 10px; transition: all .3s ease; } .SettingsButton:hover { scale: 1.1; } .colorInput{ background-color: transparent; width: 31px; height: 35px; border-radius: 50%; border: none; } .colorInput::-webkit-color-swatch { border-radius: 50%; border: 2px solid #fff; } .whiteBorder_colorInput::-webkit-color-swatch { border-color: #fff; } .menu-center>div>.menu-center-content>div>div>.menu__form-group { margin-bottom: 14px !important; } #dclinkdiv { display: flex; flex-direction: row; margin-top: 10px; } .dclinks { width: calc(50% - 5px); height: 36px; display: flex; justify-content: center; align-items: center; background-color: rgba(88, 101, 242, 1); border-radius: 6px; margin: 0 auto; color: #fff; } #cm_close__settings { width: 50px; transition: all .3s ease; } #cm_close__settings svg:hover { scale: 1.1; } #cm_close__settings svg { transition: all .3s ease; } .modTitleText { text-align: center; font-size: 16px; } .modItem { display: flex; justify-content: center; align-items: center; flex-direction: column; } .accent_row { background: #111111; } .mod_tab-content { width: 100%; margin: 10px; overflow: auto; display: flex; flex-direction: column; } #Tab6 .mod_tab-content { overflow-y: auto; max-height: 230px; display: flex; flex-wrap: nowrap; flex-direction: column; gap: 10px; } .tab-content, #coins-tab, #chests-tab { overflow-x: hidden; justify-content: center; } #shop-skins-buttons::after { background: #050505; } #sigma-status { background: rgba(0, 0, 0, .6) !important; } .w-100 { width: 100% } .btn:hover { color: unset; } #savedNames { background-color: #000; padding: 5px; border-radius: 5px; overflow-y: auto; height: 155px; background-image: url("https://raw.githubusercontent.com/Sigmally/SigMod/main/images/purple_gradient.png"); background-size: cover; background-position: center; background-repeat: no-repeat; box-shadow: 0 0 10px #000; } .scroll { scroll-behavior: smooth; } /* Chrome, Safari */ .scroll::-webkit-scrollbar { width: 7px; } .scroll::-webkit-scrollbar-track { background: #222; border-radius: 5px; } .scroll::-webkit-scrollbar-thumb { background-color: #333; border-radius: 5px; } .scroll::-webkit-scrollbar-thumb:hover { background: #353535; } /* Firefox */ .scroll { scrollbar-width: thin; scrollbar-color: #333 #222; } .scroll:hover { scrollbar-color: #353535 #222; } .themes { display: flex; flex-direction: row; flex: 1; flex-wrap: wrap; justify-content: center; width: 100%; min-height: 254px; max-height: 420px; background: #000; border-radius: 5px; overflow-y: scroll; gap: 10px; padding: 5px; } .themeContent { width: 50px; height: 50px; border: 2px solid #222; border-radius: 50%; background-position: center; } .theme { height: 75px; display: flex; align-items: center; justify-content: center; flex-direction: column; cursor: pointer; } .delName { font-weight: 500; background: #e17e7e; height: 20px; border: none; border-radius: 5px; font-size: 10px; margin-left: 5px; color: #fff; display: flex; justify-content: center; align-items: center; width: 20px; } .NameDiv { display: flex; background: #111; border-radius: 5px; margin: 5px; padding: 3px 8px; height: 34px; align-items: center; justify-content: space-between; cursor: pointer; box-shadow: 0 5px 10px -2px #000; } .NameLabel { cursor: pointer; font-weight: 500; text-align: center; color: #fff; } .alwan { border-radius: 8px; } .colorpicker-additional { display: flex; justify-content: space-between; width: 100%; color: #fafafa; padding: 10px; } .resetButton { width: 25px; height: 25px; background-image: url("https://raw.githubusercontent.com/Sigmally/SigMod/main/images/reset.svg"); background-color: transparent; background-repeat: no-repeat; border: none; } .modAlert { position: fixed; top: 8%; left: 50%; transform: translate(-50%, -50%); z-index: 99995; background: #3F3F3F; border-radius: 10px; display: flex; flex-direction: column; gap: 5px; padding: 10px; color: #fff; max-width: 320px; } .alert_overlay { position: absolute; top: 0; left: 0; z-index: 999999999; pointer-events: none; width: 100%; height: 100vh; display: flex; flex-direction: column; justify-content: start; align-items: center; } .infoAlert { padding: 5px; border-radius: 5px; margin-top: 5px; color: #fff; } .modAlert-success { background: #5BA55C; } .modAlert-success .modAlert-loader { background: #6BD56D; } .modAlert-default { background: #151515; } .modAlert-default .modAlert-loader { background: #222; } .modAlert-danger { background: #D44121; } .modAlert-danger .modAlert-loader { background: #A5361E; } #free-coins .modAlert-danger { background: #fff !important; } .modAlert-loader { width: 100%; height: 2px; margin-top: 5px; transition: all .3s ease-in-out; animation: loadAlert 2s forwards; } @keyframes loadAlert { 0% { width: 100%; } 100% { width: 0%; } } .themeEditor { z-index: 999999999999; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, .85); color: #fff; padding: 10px; border-radius: 10px; box-shadow: 0 0 10px #000; width: 400px; } .theme_editor_header { display: flex; justify-content: space-between; align-items: center; gap: 10px; } .theme-editor-tab { display: flex; justify-content: center; align-items: start; flex-direction: column; margin-top: 10px } .themes_preview { width: 50px; height: 50px; border: 2px solid #fff; border-radius: 2px; display: flex; justify-content: center; align-items: center; } .sigmod-title { display: flex; flex-direction: column; align-items: center; gap: 2px; } .sigmod-title #title { width: fit-content; } #bycursed { font-family: "Titillium Web", sans-serif; font-weight: 400; font-size: 16px; } #bycursed a { color: #71aee3; } .stats__item>span, #title, .stats-btn__text { color: #fff; } .top-users { overflow: hidden; } .top-users__inner { background: transparent; } .top-users__inner table { background-color: rgba(0, 0, 0, 0.6); color: #fff; border-radius: 6px; padding-top: 4px; padding-right: 6px; } .top-users_buttons button { background-color: rgba(0, 0, 0, 0.5); color: #fff; } .top-users_buttons button.active { background-color: rgba(0, 0, 0, 0.8); } .top-users__inner::-webkit-scrollbar-thumb { border: none; } #signInBtn, #nick, #gamemode, .form-control, .profile-header, .coins-num, #clan-members, .member-index, .member-level, #clan-requests { background: rgba(0, 0, 0, 0.4) !important; color: #fff !important; } .profile-name, #progress-next, .member-desc > p:first-child, #clan-leave > div, .clans-item > div > b, #clans-input input, #shop-nav button { color: #fff !important; } .head-desc, #shop-nav button { border: 1px solid #000; } #shop-nav button { transition: background .1s ease; } #shop-nav button:hover { background: #121212 !important; } #clan-handler, #request-handler, #clans-list, #clans-input, .clans-item button, #shop-content, .card-particles-bar-bg { background: #111 !important; color: #fff !important; } #clans_and_settings { height: auto !important; } .card-body { background: linear-gradient(180deg, #000 0%, #1b354c 100%); } .free-card:hover .card-body { background: linear-gradient(180deg, #111 0%, #1b354c 100%); } #shop-tab-body, #shop-nav, #shop-skins-buttons { background: #050505 !important; } #clan-leave { background: #111; bottom: -1px; } .sent { position: relative; width: 100px; } .sent::before { content: "Sent request"; width: 100%; height: 10px; word-spacing: normal; white-space: nowrap; position: absolute; background: #4f79f9; display: flex; justify-content: center; align-items: center; } .btn, .sign-in-out-btn { transition: all .2s ease; } .free-coins-body > div, .goldmodal-contain { background: rgba(0, 0, 0, .5); } #clan .connecting__content, #clans .connecting__content { background: #151515; color: #fff; box-shadow: 0 0 10px rgba(0, 0, 0, .5); } .skin-select__icon-text { color: #fff; } .justify-sb { display: flex; align-items: center; justify-content: space-between; } .macro-extanded_input { width: 75px; text-align: center; } .form-control option { background: #111; } .stats-line { width: 100%; user-select: none; margin-bottom: 5px; padding: 5px; background: #050505; border: 1px solid var(--default-mod); border-radius: 5px; } .stats-info-text { color: #7d7d7d; } .setting-card-wrapper { margin-right: 10px; padding: 10px; background: #161616; border-radius: 5px; display: flex; flex-direction: column; width: 100%; } .setting-card { display: flex; align-items: center; justify-content: space-between; } .setting-card-action { display: flex; align-items: center; gap: 5px; cursor: pointer; } .setting-card-action { width: 100%; } .setting-card-name { font-size: 16px; user-select: none; width: 100%; } .mod-small-modal { display: flex; flex-direction: column; gap: 10px; position: absolute; z-index: 99999; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #191919; box-shadow: 0 5px 15px -2px #000; padding: 10px; border-radius: 5px; } .mod-small-modal-header { display: flex; justify-content: space-between; align-items: center; } .mod-small-modal-header h1 { font-size: 20px; font-weight: 500; margin: 0; } .mod-small-modal-content { display: flex; flex-direction: column; width: 100%; align-items: center; } .mod-small-modal-content_selectImage { display: flex; flex-direction: column; gap: 10px; } .imagePreview { min-width: 34px; width: 34px; height: 34px; border: 2px solid #353535; border-radius: 5px; background-size: contain; background-repeat: no-repeat; background-position: center; /* for preview text */ display: flex; justify-content: center; align-items: center; font-size: 7px; overflow: hidden; text-align: center; } .modChat { min-width: 450px; max-width: 450px; min-height: 285px; max-height: 285px; color: #fafafa; padding: 10px; position: absolute; bottom: 10px; left: 10px; z-index: 999; border-radius: .5rem; overflow: hidden; opacity: 1; transition: all .3s ease; } .modChat__inner { min-width: 430px; max-width: 430px; min-height: 265px; max-height: 265px; height: 100%; display: flex; flex-direction: column; gap: 5px; justify-content: flex-end; opacity: 1; transition: all .3s ease; } .mod-compact { transform: scale(0.78); } .mod-compact.modChat { left: -40px; bottom: -20px; } .mod-compact.chatAddedContainer { left: 350px; bottom: -17px; } #scroll-down-btn { position: absolute; bottom: 60px; left: 50%; transform: translateX(-50%); width: 80px; display:none; box-shadow:0 0 5px #000; z-index: 5; } .modchat-chatbuttons { margin-bottom: auto; display: flex; gap: 5px; } .chat-context { position: absolute; z-index: 999999; width: 100px; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 5px; background: #181818; border-radius: 5px; } .chat-context span { color: #fff; user-select: none; padding: 5px; white-space: nowrap; } .chat-context button { width: 100%; background-color: transparent; border: none; border-top: 2px solid #747474; outline: none; color: #fff; transition: all .3s ease; } .chat-context button:hover { backgrokund-color: #222; } .tagText { margin-left: auto; font-size: 14px; } #mod-messages { position: relative; display: flex; flex-direction: column; max-height: 185px; overflow-y: auto; direction: rtl; scroll-behavior: smooth; } .message { direction: ltr; margin: 2px 0 0 5px; text-overflow: ellipsis; max-width: 100%; display: flex; justify-content: space-between; } .message_name { user-select: none; } .chatMessage-text { max-width: 310px; } .message .time { color: rgba(255, 255, 255, 0.7); font-size: 12px; } #chatInputContainer { display: flex; gap: 5px; align-items: center; padding: 5px; background: rgba(25,25,25, .6); border-radius: .5rem; overflow: hidden; } .chatInput { flex-grow: 1; border: none; background: transparent; color: #fff; padding: 5px; outline: none; max-width: 100%; } .chatButton { background: #8a25e5; border: none; border-radius: 5px; padding: 5px 10px; height: 100%; color: #fff; transition: all 0.3s; cursor: pointer; display: flex; align-items: center; height: 28px; justify-content: center; gap: 5px; } .chatButton:hover { background: #7a25e5; } .chatCloseBtn { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } .emojisContainer { display: flex; flex-direction: column; gap: 5px; } .chatAddedContainer { position: absolute; bottom: 10px; left: 465px; z-index: 9999; padding: 10px; background: #151515; border-radius: .5rem; min-width: 172px; max-width: 172px; min-height: 250px; max-height: 250px; } #categories { overflow-y: auto; max-height: calc(250px - 50px); gap: 2px; } .category { width: 100%; display: flex; flex-direction: column; gap: 2px; } .category span { color: #fafafa; font-size: 14px; text-align: center; } .emojiContainer { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; } #categories .emoji { padding: 2px; border-radius: 5px; font-size: 16px; user-select: none; cursor: pointer; } .chatSettingsContainer { padding: 10px 3px; } .chatSettingsContainer .scroll { display: flex; flex-direction: column; gap: 10px; max-height: 235px; overflow-y: auto; padding: 0 10px; } .csBlock { border: 2px solid #050505; border-radius: .5rem; color: #fff; display: flex; align-items: center; flex-direction: column; gap: 5px; padding-bottom: 5px; } .csBlock .csBlockTitle { background: #080808; width: 100%; padding: 3px; text-align: center; } .csRow { display: flex; justify-content: space-between; align-items: center; padding: 0 5px; width: 100%; } .csRowName { display: flex; gap: 5px; align-items: start; } .csRowName .infoIcon { width: 14px; cursor: pointer; } .modInfoPopup { position: absolute; top: 2px; left: 58%; text-align: center; background: #151515; border: 1px solid #607bff; border-radius: 10px; transform: translateX(-50%); white-space: nowrap; padding: 5px; z-index: 99999; } .modInfoPopup::after { content: ''; display: block; position: absolute; bottom: -7px; background: #151515; right: 50%; transform: translateX(-50%) rotate(-45deg); width: 12px; height: 12px; border-left: 1px solid #607bff; border-bottom: 1px solid #607bff; } .modInfoPopup p { margin: 0; font-size: 12px; color: #fff; } .minimapContainer { display: flex; flex-direction: column; align-items: end; pointer-events: none; position: absolute; bottom: 0; right: 0; z-index: 99999; } .minimap { border-radius: 2px; border-top: 1px solid rgba(255, 255, 255, .5); border-left: 1px solid rgba(255, 255, 255, .5); box-shadow: 0 0 4px rgba(255, 255, 255, .5); } #tag { width: 50px; } .blur { color: transparent!important; text-shadow: 0 0 6px hsl(0deg 0% 90% / 70%); transition: all .2s; } .blur:focus, .blur:hover { color: #fafafa!important; text-shadow: none; } .progress-row button { background: transparent; } #mod_home .justify-sb { z-index: 2; } .modTitleText { font-size: 15px; color: #fafafa; text-align: start; } .modDescText { text-align: start; font-size: 12px; color: #777; } .modButton-secondary { background-color: #171717; color: #fff; border: none; padding: 5px 15px; border-radius: 15px; } .vr { width: 2px; height: 250px; background-color: #fafafa; } .vr2 { width: 1px; height: 26px; background-color: #202020; } .home-card-row { display: flex; flex-wrap: nowrap; justify-content: space-between; gap: 18px; } .home-card-wrapper { display: flex; flex: 1; flex-direction: column; gap: 5px; width: 50%; } .home-card { display: flex; flex-direction: column; justify-content: center; gap: 7px; border-radius: 5px; background: #050505; min-height: 164px; max-height: 164px; max-width: 256px; padding: 5px 10px; } .quickAccess { gap: 5px; max-height: 164px; overflow-y: auto; justify-content: start; } .quickAccess div.modRowItems { padding: 2px!important; } #my-profile-badges { display: flex; flex-wrap: wrap; gap: 5px; } #my-profile-bio { overflow-y: scroll; max-height: 75px; } .brand_wrapper { position: relative; height: 72px; width: 100%; display: flex; justify-content: center; align-items: center; } .brand_wrapper span { font-size: 24px; z-index: 2; font-family: "Titillium Web", sans-serif; font-weight: 600; letter-spacing: 3px; } .brand_img { position: absolute; top: 0; left: 0; width: 100%; height: 72px; border-radius: 10px; object-fit: cover; object-position: center; z-index: 1; box-shadow: 0 0 10px #000; } .brand_credits { position: relative; font-size: 16px; color: #D3A7FF; list-style: none; width: 100%; display: flex; justify-content: space-between; padding: 0 24px; } .brand_credits li { position: relative; display: inline-block; text-shadow: 0px 0px 8px #D3A7FF; } .brand_credits li:not(:last-child)::after { content: '•'; position: absolute; right: -20px; color: #D3A7FF; } .brand_yt { display: flex; justify-content: center; align-items: center; gap: 20px; } .yt_wrapper { display: flex; justify-content: center; align-items: center; gap: 10px; width: 122px; padding: 5px; background-color: #B63333; border-radius: 15px; cursor: pointer; } .yt_wrapper span { user-select: none; } .hidden_full { display: none !important; visibility: hidden; } .mod_overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100vh; background: rgba(0, 0, 0, .7); z-index: 9999999; display: flex; justify-content: center; align-items: center; transition: all .3s ease; } .black_overlay { background: rgba(0, 0, 0, 0); z-index: 99999999; opacity: 1; animation: 2s ease fadeInBlack forwards; } @keyframes fadeInBlack { 0% { background: rgba(0, 0, 0, 0); } 100% { background: rgba(0, 0, 0, 1); } } .default-modal { position: relative; display: flex; flex-direction: column; min-width: 300px; background: #111; color: #fafafa; border-radius: 12px; overflow: hidden; box-shadow: 0 5px 10px #000; } .default-modal-header { display: flex; justify-content: space-between; align-items: center; background: #1c1c1c; padding: 5px 10px; } .default-modal-header h2 { margin: 0; } .default-modal-body { display: flex; flex-direction: column; gap: 6px; padding: 15px; } .tournament-overlay-info { display: flex; flex-direction: column; align-items: center; color: white; font-size: 28px; line-height: 1.4; } .tournament-overlay-info img { margin-bottom: 26px; } .tournaments-wrapper { font-family: 'Titillium Web', sans-serif; font-weight: 400; position: absolute; top: 60%; left: 50%; transform: translate(-50%, -50%); background: #000; border: 2px solid #222222; border-radius: 1.125rem; padding: 1.5rem; color: #fafafa; display: flex; flex-direction: column; align-items: center; gap: 10px; min-width: 632px; opacity: 0; transition: all .3s ease; animation: 0.5s ease fadeIn forwards; } @keyframes fadeIn { 0% { top: 60%; opacity: 0; } 100% { top: 50%; opacity: 1; } } .tournaments h1 { margin: 0; font-weight: 600; } .teamCards { display: flex; gap: 5px; position: absolute; top: 50%; transform: translate(-50%, -50%); } .teamCard { display: flex; flex-direction: column; align-items: center; background: rgba(0, 0, 0, 0.4); border-radius: 12px; padding: 6px; height: fit-content; } .teamCard.userReady { border: 2px solid var(--green); } .teamCard img { border-radius: 50%; } .teamCard span { font-size: 12px; } .redTeam { left: 81%; } .blueTeam { left: 24%; } .lastOneStanding_list { display: flex; flex-wrap: wrap; gap: 8px; width: 100%; height: 240px; max-height: 240px; background: #050505; } .tournament_timer { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); color: #fff; font-size: 15px; z-index: 99999; user-select: none; pointer-events: none; } details { border: 1px solid #aaa; border-radius: 4px; padding: 0.5em 0.5em 0; user-select: none; text-align: start; } summary { font-weight: bold; margin: -0.5em -0.5em 0; padding: 0.5em; } details[open] { padding: 0.5em; } details[open] summary { border-bottom: 1px solid #aaa; margin-bottom: 0.5em; } button[disabled] { filter: grayscale(1); } .tournament-text-lost, .tournament-text-won { font-size: 6rem; font-weight: 600; font-family: 'Titillium Web', sans-serif; } .tournament-text-lost { color: var(--red); } .tournament-text-won { color: var(--green); } .tournament_alert { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); background: #151515; color: #fff; text-align: center; padding: 20px; z-index: 999999; border-radius: 10px; box-shadow: 0 0 10px #000; display: flex; gap: 10px; } .tournament-profile { width: 50px; height: 50px; border-radius: 50%; box-shadow: 0 0 10px #000; } .tournament-text { color: #fff; font-size: 24px; } .claimedBadgeWrapper { background: linear-gradient(232deg, #020405 1%, #04181E 100%); border-radius: 10px; width: 320px; height: 330px; box-shadow: 0 0 40px -20px #39bdff; display: flex; flex-direction: column; gap: 10px; align-items: center; justify-content: center; color: #fff; padding: 10px; } .btn-cyan { background: #53B6CC; border: none; border-radius: 5px; font-size: 16px; color: #fff; font-weight: 500; width: fit-content; padding: 5px 10px; } .playTimer { z-index: 2; position: absolute; top: 128px; left: 4px; color: #8d8d8d; font-size: 14px; font-weight: 500; user-select: none; pointer-events: none; } .mouseTracker { z-index: 2; position: absolute; top: 144px; left: 4px; color: #8d8d8d; font-size: 14px; font-weight: 500; user-select: none; pointer-events: none; } .modInput-wrapper { position: relative; display: inline-block; width: 100%; } .modInput-secondary { display: inline-block; width: 100%; padding: 10px 0 10px 15px; font-weight: 400; color: #E9E9E9; background: #050505; border: 0; border-radius: 3px; outline: 0; text-indent: 70px; transition: all .3s ease-in-out; } .modInput-secondary.t-indent-120 { text-indent: 120px; } .modInput-secondary::-webkit-input-placeholder { color: #050505; text-indent: 0; font-weight: 300; } .modInput-secondary + label { display: inline-block; position: absolute; top: 8px; left: 0; bottom: 8px; padding: 5px 15px; color: #E9E9E9; font-size: 11px; font-weight: 700; text-transform: uppercase; text-shadow: 0 1px 0 rgba(19, 74, 70, 0); transition: all .3s ease-in-out; border-radius: 3px; background: rgba(122, 134, 184, 0); } .modInput-secondary + label:after { position: absolute; content: ""; width: 0; height: 0; top: 100%; left: 50%; margin-left: -3px; border-left: 3px solid transparent; border-right: 3px solid transparent; border-top: 3px solid rgba(122, 134, 184, 0); transition: all .3s ease-in-out; } .modInput-secondary:focus, .modInput-secondary:active { color: #E9E9E9; text-indent: 0; background: #050505; } .modInput-secondary:focus::-webkit-input-placeholder, .modInput-secondary:active::-webkit-input-placeholder { color: #aaa; } .modInput-secondary:focus + label, .modInput-secondary:active + label { color: #fff; text-shadow: 0 1px 0 rgba(19, 74, 70, 0.4); background: #7A86B8; transform: translateY(-40px); } .modInput-secondary:focus + label:after, .modInput-secondary:active + label:after { border-top: 4px solid #7A86B8; } /* Friends & account */ .signIn-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100vh; background: rgba(0, 0, 0, .4); z-index: 999999; display: flex; justify-content: center; align-items: center; color: #E3E3E3; opacity: 0; transition: all .3s ease; } .signIn-wrapper { background: #111111; width: 450px; display: flex; flex-direction: column; align-items: center; border-radius: 10px; color: #fafafa; } .signIn-header { background: #181818; width: 100%; display: flex; justify-content: space-between; align-items: center; padding: 8px; border-radius: 10px 10px 0 0; } .signIn-header span { font-weight: 500; font-size: 20px; } .signIn-body { display: flex; flex-direction: column; gap: 10px; align-items: center; justify-content: start; padding: 40px 40px 5px 40px; width: 100%; } #errMessages { color: #AC3D3D; flex-direction: column; gap: 5px; } .friends_header { display: flex; flex-direction: row; justify-content: space-between; align-items: center; gap: 10px; width: 100%; padding: 10px; } .friends_body { position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px; width: 100%; height: 360px; max-height: 360px; overflow-y: auto; padding-right: 10px; } .allusers { padding: 0; } #users-container { position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px; width: 100%; height: 340px; max-height: 340px; overflow-y: auto; padding-right: 10px; } .profile-img { position: relative; width: 52px; height: 52px; border-radius: 100%; border: 1px solid #C8C9D9; } .profile-img img { width: 100%; height: 100%; border-radius: 100%; } .status_icon { position: absolute; width: 15px; height: 15px; top: 0; left: 0; border-radius: 50%; } .online_icon { background-color: #3DB239; } .offline_icon { background-color: #B23939; } .Owner_role { color: #3979B2; } .Moderator_role { color: #39B298; } .Vip_role { color: #E1A33E; } .friends_row { display: flex; flex-direction: row; width: 100%; justify-content: space-between; align-items: center; background: #090909; border-radius: 8px; padding: 10px; } .friends_row .val { width: fit-content; height: fit-content; padding: 5px 20px; box-sizing: content-box; } .user-profile-wrapper { cursor: pointer; } .user-profile-wrapper > .centerY.g-5 { pointer-events: none; user-select: none; cursor: pointer; } .textarea-container { position: relative; width: 100%; } .textarea-container textarea { width: 100%; height: 120px; resize: none; } .char-counter { position: absolute; bottom: 5px; right: 5px; color: gray; } .mod_badges { display: flex; flex-wrap: wrap; gap: 5px; } .mod_badge { width: fit-content; background: #222; color: #fafafa; padding: 2px 7px; border-radius: 9px; } .friends-chat-wrapper { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: #111111; display: flex; flex-direction: column; z-index: 999; opacity: 0; transition: all .3s ease; } .friends-chat-header { display: flex; justify-content: space-between; align-items: center; background: #050505; width: 100%; padding: 8px; height: 68px; } .friends-chat-body { height: calc(100% - 68px); display: flex; flex-direction: column; } .friends-chat-messages { height: 300px; overflow-y: auto; display: flex; flex-direction: column; gap: 5px; padding: 10px; } .friends-message { background: linear-gradient(180deg, rgb(12 12 12), #000); padding: 8px; border-radius: 12px; display: flex; flex-direction: column; min-width: 80px; max-width: 200px; width: fit-content; } .message-date { color: #8a8989; font-size: 11px; } .message-right { align-self: flex-end; } .messenger-wrapper { width: 100%; height: 60px; padding: 10px; } .messenger-wrapper .container { display: flex; flex-direction: row; gap: 10px; width: 100%; background: #0a0a0a; padding: 10px 5px; border-radius: 10px; } .messenger-wrapper .container input { padding: 5px; } .messenger-wrapper .container button { width: 150px; } /* deathscreen challenges */ #menu-wrapper { overflow-x: visible; overflow-y: visible; } .challenges_deathscreen { width: 450px; background: rgb(21, 21, 21); display: flex; flex-direction: column; gap: 8px; padding: 7px; border-radius: 10px; margin-bottom: 15px; } .challenges-col { display: flex; flex-direction: column; gap: 4px; align-items: center; width: 100%; } .challenge-row { display: flex; align-items: center; background: rgba(0, 0, 0, .4); justify-content: space-between; padding: 5px 10px; border-radius: 4px; width: 100%; } .challenges-title { font-size: 16px; font-weight: 600; } .challenge-best-secondary { background: #1d1d1d; border-radius: 10px; text-align: center; padding: 5px 8px; min-width: 130px; } .challenge-collect-secondary { background: #4C864B; border-radius: 10px; text-align: center; padding: 5px 8px; outline: none; border: none; min-width: 130px; } .alwan__reference { border-width: 2px !important; } #mod-announcements { display: flex; flex-direction: column; gap: 6px; max-height: 144px; overflow-y: auto; } .mod-announcement { background: #111111; border-radius: 4px; display: flex; gap: 3px; width: 100%; cursor: pointer; padding: 5px 8px; } .mod-announcement-icon { border-radius: 50%; width: 35px; height: 35px; align-self: center; } .mod-announcement-text { display: flex; flex-direction: column; gap: 3px; overflow: hidden; flex-grow: 1; } .mod-announcement-text > * { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .mod-announcement-text span { font-size: 14px; color: #ffffff; flex-shrink: 0; } .mod-announcement-text p { font-size: 10px; color: #898989; flex-shrink: 0; margin: 0; } .mod-announcements-wrapper { display: flex; flex-direction: column; gap: 10px; } .mod-announcement-content { display: flex; justify-content: space-between; gap: 10px; background-color: #050505; padding: 10px; border-radius: 10px; background-image: url('https://czrsd.com/static/general/bg_blur.png'); background-repeat: no-repeat; background-position: 300px 40%; background-size: cover; } .mod-announcement-content p { text-align: justify; max-height: 330px; overflow-y: auto; padding-right: 10px; } .mod-announcement-images { display: flex; flex-direction: column; gap: 20px; min-width: 30%; width: 30%; max-height: 330px; padding-right: 10px; overflow-y: auto; } .mod-announcement-images img { border-radius: 5px; cursor: pointer; } #image-gallery { display: flex; flex-wrap: wrap; gap: 5px; } .image-container { display: flex; flex-direction: column; } .image-container img { width: 172px; height: auto; aspect-ratio: 16 / 9; border-radius: 5px; cursor: pointer; } .download_btn { background: url('https://czrsd.com/static/sigmod/icons/download.svg'); } .delete_btn { background: url('https://czrsd.com/static/sigmod/icons/trash-bin.svg'); } .operation_btn { background-size: contain; background-repeat: no-repeat; border: none; width: 22px; height: 22px; } .sigmod-community { display: flex; flex-direction: column; margin: auto; border-radius: 10px; width: 50%; align-self: center; font-size: 19px; overflow: hidden; } .community-header { background: linear-gradient(179deg, #000000, #0c0c0c); text-align: center; font-family: "Titillium Web", sans-serif; padding: 6px; } .community-discord-logo { background: rgb(88, 101, 242); padding: 5px 12px; } .community-discord { text-align: center; padding: 10px; background: #0c0c0c; width: 100%; } .community-discord a { font-family: "Titillium Web", sans-serif; } /* common */ .flex { display: flex; } .centerX { display: flex; justify-content: center; } .centerY { display: flex; align-items: center; } .centerXY { display: flex; align-items: center; justify-content: center } .f-column { display: flex; flex-direction: column; } mx-5 { margin: 0 5px; } .my-5 { margin: 5px 0; } .mt-auto { margin-top: auto !important; } .g-2 { gap: 2px; } .g-5 { gap: 5px; } .g-10 { gap: 10px; } .p-2 { padding: 2px; } .p-5 { padding: 5px; } .p-10 { padding: 10px; } .rounded { border-radius: 6px; } .text-center { text-align: center; } .f-big { font-size: 18px; } .hidden { display: none; } `; }, respawnTime: Date.now(), respawnCooldown: 1000, get friends_settings() { return this._friends_settings; }, set friends_settings(value) { this._friends_settings = value; window.sigmod.friends_settings = value; }, get friend_names() { return this._friend_names; }, set friend_names(value) { this._friend_names = value; window.sigmod.friend_names = value; }, async game() { const { fillRect, fillText, strokeText, arc, drawImage } = CanvasRenderingContext2D.prototype; const showPosition = byId('showPosition'); if (showPosition && !showPosition.checked) showPosition.click(); const loadStorage = () => { if (modSettings.virusImage) { loadVirusImage(modSettings.virusImage); } if (modSettings.game.skins.original !== null) { loadSkinImage( modSettings.game.skins.original, modSettings.game.skins.replacement ); } }; loadStorage(); let cachedPattern = null; let patternCanvas = null; let isUpdatingPattern = false; function updatePattern(ctx) { isUpdatingPattern = true; loadPattern(ctx) .then((pattern) => { if (mods.mapImageLoaded) { cachedPattern = pattern; ctx.fillStyle = cachedPattern; ctx.fillRect( 0, 0, ctx.canvas.width, ctx.canvas.height ); } else { clearPattern(ctx); } isUpdatingPattern = false; }) .catch((e) => { console.error('Error loading map image:', e); clearPattern(ctx); isUpdatingPattern = false; }); } function loadPattern(ctx) { return new Promise((resolve, reject) => { const img = new Image(); img.src = modSettings.game.map.image; img.crossOrigin = 'Anonymous'; img.onload = () => { if (!patternCanvas) { patternCanvas = document.createElement('canvas'); } patternCanvas.width = img.width; patternCanvas.height = img.height; const patternCtx = patternCanvas.getContext('2d'); patternCtx.drawImage(img, 0, 0); const imageData = patternCtx.getImageData( 0, 0, patternCanvas.width, patternCanvas.height ); const data = imageData.data; mods.mapImageLoaded = !Array.from(data).some( (_, i) => i % 4 === 3 && data[i] < 255 ); if (mods.mapImageLoaded) { resolve( ctx.createPattern(patternCanvas, 'no-repeat') ); } else { resolve(null); } }; img.onerror = () => reject(new Error('Failed to load image.')); }); } function clearPattern(ctx) { isUpdatingPattern = true; ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.fillStyle = modSettings.game.map.color || '#111111'; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); isUpdatingPattern = false; } window.addEventListener('resize', () => { const canvas = document.getElementById('canvas'); if (canvas && modSettings.game.map.image) { updatePattern(canvas.getContext('2d')); } }); /* CanvasRenderingContext2D.prototype */ CanvasRenderingContext2D.prototype.fillRect = function ( x, y, width, height ) { if (this.canvas.id !== 'canvas') return fillRect.apply(this, arguments); const isCanvasSize = (width + height) / 2 === (window.innerWidth + window.innerHeight) / 2; if (isCanvasSize) { if (modSettings.game.map.image && !mods.mapImageLoaded) { mods.mapImageLoaded = true; updatePattern(this); } else if ( !modSettings.game.map.image || !mods.mapImageLoaded ) { if (!isUpdatingPattern) { clearPattern(this); } } else { this.fillStyle = cachedPattern || modSettings.game.map.color || '#111111'; } } fillRect.apply(this, arguments); }; CanvasRenderingContext2D.prototype.arc = function ( x, y, radius, startAngle, endAngle, anticlockwise ) { if (this.canvas.id !== 'canvas') return arc.apply(this, arguments); if (radius >= 86 && modSettings.game.cellColor) { this.fillStyle = modSettings.game.cellColor; } else if ( radius <= 20 && modSettings.game.foodColor !== null ) { this.fillStyle = modSettings.game.foodColor; this.strokeStyle = modSettings.game.foodColor; } arc.apply(this, arguments); }; CanvasRenderingContext2D.prototype.fillText = function ( text, x, y ) { if (this.canvas.id !== 'canvas') return fillText.apply(this, arguments); const currentFontSizeMatch = this.font.match(/^(\d+)px/); const fontSize = currentFontSizeMatch ? currentFontSizeMatch[0] : ''; this.font = `${fontSize} ${modSettings.game.font || 'Ubuntu'}`; if ( text === mods.nick && !modSettings.game.name.gradient.enabled && modSettings.game.name.color !== null ) { this.fillStyle = modSettings.game.name.color; } if ( text === mods.nick && modSettings.game.name.gradient.enabled ) { const width = this.measureText(text).width; const fontSize = 8; const gradient = this.createLinearGradient( x - width / 2 + fontSize / 2, y, x + width / 2 - fontSize / 2, y + fontSize ); const color1 = modSettings.game.name.gradient.left ?? '#ffffff'; const color2 = modSettings.game.name.gradient.right ?? '#ffffff'; gradient.addColorStop(0, color1); gradient.addColorStop(1, color2); this.fillStyle = gradient; } if (!window.sigifx && text.startsWith('Score')) { if (Date.now() - lastGetScore >= 250) { const score = parseInt(text.split(': ')[1]); mods.cellSize = score; mods.aboveRespawnLimit = score >= 5500; lastGetScore = Date.now(); } } if (!window.sigfix && text.startsWith('X:')) { this.fillStyle = 'transparent'; const [, xValue, yValue] = /X: (.*), Y: (.*)/.exec(text) || []; if (!xValue) return; const position = { x: parseFloat(xValue), y: parseFloat(yValue), }; if (menuClosed() && !isDead()) { if (position.x === 0 && position.y === 0) return; playerPosition.x = position.x; playerPosition.y = position.y; // send position every 300 milliseconds if (Date.now() - lastPosTime >= 300) { if (modSettings.settings.tag && client?.ws?.readyState === 1) { client.send({ type: 'position', content: { x: playerPosition.x, y: playerPosition.y, }, }); } lastPosTime = Date.now(); } } else if (isDead() && !dead2) { dead2 = true; playerPosition.x = null; playerPosition.y = null; if (modSettings.settings.tag && client?.ws?.readyState === 1) { client.send({ type: 'position', content: { x: null, y: null }, }); } } } if (modSettings.game.removeOutlines) { this.shadowBlur = 0; this.shadowColor = 'transparent'; } if (text.length > 18 && modSettings.game.shortenNames) { arguments[0] = text.slice(0, 18) + '...'; } // only for leaderboard const name = text.match(/\d+\.\s*(.+)/)?.[1]; if ( name && mods.friend_names.has(name) && mods.friends_settings.highlight_friends ) { this.fillStyle = mods.friends_settings.highlight_color; } fillText.apply(this, arguments); }; CanvasRenderingContext2D.prototype.strokeText = function ( text, x, y ) { if (this.canvas.id !== 'canvas') return strokeText.apply(this, arguments); const currentFontSizeMatch = this.font.match(/^(\d+)px/); const fontSize = currentFontSizeMatch ? currentFontSizeMatch[0] : ''; this.font = `${fontSize} ${modSettings.game.font || 'Ubuntu'}`; if (text.length > 18 && modSettings.game.shortenNames) { arguments[0] = text.slice(0, 18) + '...'; } if (modSettings.game.removeOutlines) { this.shadowBlur = 0; this.shadowColor = 'transparent'; } else { this.shadowBlur = 7; this.shadowColor = '#000'; } strokeText.apply(this, arguments); }; CanvasRenderingContext2D.prototype.drawImage = function ( image, ...args ) { if (this.canvas.id !== 'canvas') return drawImage.call(this, image, ...args); if ( image.src && (image.src.endsWith('2.png') || image.src.endsWith('2-min.png')) && modSettings.game.virusImage ) { if (mods.virusImageLoaded) { return drawImage.call(this, mods.virusImage, ...args); } else { loadVirusImage(modSettings.game.virusImage).then(() => { drawImage.call(this, mods.virusImage, ...args); }); return; } } if ( image instanceof HTMLImageElement && modSettings.game.skins.original && image.src.includes(`${modSettings.game.skins.original}.png`) ) { if (mods.skinImageLoaded) { return drawImage.call(this, mods.skinImage, ...args); } else { loadSkinImage( modSettings.game.skins.original, modSettings.game.skins.replacement ).then(() => { drawImage.call(this, mods.skinImage, ...args); }); return; } } drawImage.call(this, image, ...args); }; function loadVirusImage(imgSrc) { return new Promise((resolve, reject) => { const replacementVirus = new Image(); replacementVirus.src = imgSrc; replacementVirus.crossOrigin = 'Anonymous'; replacementVirus.onload = () => { mods.virusImage = replacementVirus; mods.virusImageLoaded = true; resolve(); }; replacementVirus.onerror = () => { console.error('Failed to load virus image.'); reject(new Error('Failed to load virus image.')); }; }); } function loadSkinImage(originalSkinName, replacementImgSrc) { return new Promise((resolve, reject) => { const replacementSkin = new Image(); replacementSkin.src = replacementImgSrc; replacementSkin.crossOrigin = 'Anonymous'; replacementSkin.onload = () => { mods.skinImage = replacementSkin; mods.skinImageLoaded = true; resolve(); }; replacementSkin.onerror = () => reject(new Error('Failed to load skin image.')); }); } const modals = { map: { title: 'Map Image', applyId: 'apply-map-image', resetId: 'reset-map-image', previewId: 'preview-mapImage', modalId: 'map-modal', storagePath: 'game.map.image', }, virus: { title: 'Virus Image', applyId: 'apply-virus-image', resetId: 'reset-virus-image', previewId: 'preview-virusImage', modalId: 'virus-modal', storagePath: 'game.virusImage', }, skin: { title: 'Skin Replacement', applyId: 'apply-skin-image', resetId: 'reset-skin-image', previewId: 'preview-skinImage', modalId: 'skin-modal', storagePath: [ 'game.skins.original', 'game.skins.replacement', ], additional: true, }, }; function createModal({ title, applyId, resetId, previewId, modalId, additional, }) { const additionalContent = additional ? ` Select a skin that should be replaced: Replacement image - Enter an image URL: ` : ` Enter an image URL: `; return `

${title}

${additionalContent}
No Preview Available
`; } function setupModalEvents(id, type) { byId(id).addEventListener('click', async () => { mods.customModal( createModal(modals[type]), modals[type].modalId ); document .querySelector('#closeCustomModal') .addEventListener('click', () => closeModal(type)); const modal = modals[type]; const imageUrlInput = byId('image-url'); let initialUrl = ''; if (modal.additional && type === 'skin') { const [originalPath, replacementPath] = modal.storagePath; initialUrl = getNestedValue(modSettings, replacementPath) || ''; byId('skin-list').value = getNestedValue(modSettings, originalPath) || ''; } else { initialUrl = getNestedValue(modSettings, modal.storagePath) || ''; } imageUrlInput.value = initialUrl; updatePreview(initialUrl); imageUrlInput.addEventListener('input', (e) => { updatePreview(e.target.value); }); byId(modal.applyId).addEventListener('click', () => applyChanges(type) ); byId(modal.resetId).addEventListener('click', () => resetChanges(type) ); if (type === 'skin') { const skinList = byId('skin-list'); const skins = mods.skins.length > 0 ? mods.skins : await fetch( 'https://one.sigmally.com/api/skins' ) .then((response) => response.json()) .then((data) => { const skinNames = data.data.map( (item) => item.name.replace('.png', '') ); mods.skins = skinNames; return skinNames; }); skinList.innerHTML = skins .map( (skin) => `` ) .join(''); } }); } function getNestedValue(obj, path) { return path .split('.') .reduce((acc, part) => acc && acc[part], obj); } function setNestedValue(obj, path, value) { const parts = path.split('.'); const last = parts.pop(); const target = parts.reduce( (acc, part) => (acc[part] = acc[part] || {}), obj ); target[last] = value; } function updatePreview(url) { const preview = document.querySelector('.imagePreview'); const noPreviewSpan = document.querySelector('.no-preview'); const updateVisibility = (showPreview) => { preview.style.backgroundImage = showPreview ? `url(${url})` : 'none'; noPreviewSpan.style.display = showPreview ? 'none' : 'block'; }; if (url) { const img = new Image(); img.src = url; img.onload = () => updateVisibility(true); img.onerror = () => updateVisibility(false); } else { updateVisibility(false); } } function applyChanges(type) { let { title, storagePath, additional } = modals[type]; const url = byId('image-url').value; mods[`${type}ImageLoaded`] = false; if (additional && type === 'skin') { const selectedSkin = byId('skin-list').value; setNestedValue(modSettings, storagePath[0], selectedSkin); setNestedValue(modSettings, storagePath[1], url); } else { setNestedValue(modSettings, storagePath, url); } updateStorage(); mods.modAlert(`Successfully applied ${title}.`, 'success'); } function resetChanges(type) { const { title, storagePath, additional } = modals[type]; if (additional && type === 'skin') { setNestedValue(modSettings, storagePath[0], null); setNestedValue(modSettings, storagePath[1], null); } else { setNestedValue(modSettings, storagePath, null); } mods[`${type}ImageLoaded`] = false; updateStorage(); mods.modAlert( `The ${title} has been successfully reset.`, 'success' ); } function closeModal(type) { const overlay = byId(`${type}-modal`); overlay.style.opacity = 0; setTimeout(() => overlay.remove(), 300); } setupModalEvents('mapImageSelect', 'map'); setupModalEvents('virusImageSelect', 'virus'); setupModalEvents('skinReplaceSelect', 'skin'); const shortenNames = byId('shortenNames'); const removeOutlines = byId('removeOutlines'); shortenNames.addEventListener('change', () => { modSettings.game.shortenNames = shortenNames.checked; updateStorage(); }); removeOutlines.addEventListener('change', () => { modSettings.game.removeOutlines = removeOutlines.checked; updateStorage(); }); const showNames = byId('mod-showNames'); const showSkins = byId('mod-showSkins'); const originalShowNames = byId('showNames'); const originalShowSkins = byId('showSkins'); function syncCheckboxes() { if (showNames.checked !== originalShowNames.checked) { originalShowNames.click(); } if (showSkins.checked !== originalShowSkins.checked) { originalShowSkins.click(); } } showNames.addEventListener('change', syncCheckboxes); showSkins.addEventListener('change', syncCheckboxes); syncCheckboxes(); const deathScreenPos = byId('deathScreenPos'); const deathScreen = byId('__line2'); const applyMargin = (position) => { switch (position) { case 'left': deathScreen.style.marginLeft = '0'; break; case 'right': deathScreen.style.marginRight = '0'; break; case 'top': deathScreen.style.marginTop = '20px'; break; case 'bottom': deathScreen.style.marginBottom = '20px'; break; default: deathScreen.style.margin = 'auto'; } }; deathScreenPos.addEventListener('change', () => { const selected = deathScreenPos.value; applyMargin(selected); modSettings.deathScreenPos = selected; updateStorage(); }); const defaultPosition = modSettings.deathScreenPos || 'center'; applyMargin(defaultPosition); deathScreenPos.value = defaultPosition; }, customModal(children, id, zindex = '999999') { const overlay = document.createElement('div'); overlay.classList.add('mod_overlay'); id && overlay.setAttribute('id', id); overlay.innerHTML = children; overlay.style.zIndex = zindex; overlay.style.opacity = 0; document.body.append(overlay); setTimeout(() => { overlay.style.opacity = '1'; }); const handleClickOutside = (e) => { if (e.target === overlay) { overlay.style.opacity = 0; setTimeout(() => { overlay.remove(); document.removeEventListener( 'click', handleClickOutside ); }, 300); } }; document.addEventListener('click', handleClickOutside); }, handleGoogleAuth(user) { fetchedUser++; window.gameSettings.user = user; const chatSendInput = document.querySelector('#chatSendInput'); if (chatSendInput) { chatSendInput.placeholder = 'message...'; chatSendInput.disabled = false; } if (!client) client = new modClient(); const waitForInit = () => new Promise((res) => { if (client.ws?.readyState === 1 && mods.nick) return res(null); const i = setInterval( () => client.ws?.readyState === 1 && mods.nick && (clearInterval(i), res(null)), 50 ); }); waitForInit().then(() => { client.send({ type: 'user', content: { ...user, nick: mods.nick }, }) }); const claim = document.getElementById('free-chest-button'); if ( fetchedUser === 1 && modSettings.settings.autoClaimCoins && claim?.style.display !== 'none' ) { setTimeout(() => claim.click(), 500); } }, async menu() { const mod_menu = document.createElement('div'); mod_menu.classList.add('mod_menu'); mod_menu.style.display = 'none'; mod_menu.style.opacity = '0'; mod_menu.innerHTML = `
Header image
Welcome ${ this.nick || 'Guest' }, to the SigMod Client!
Your stats
Announcements
No announcements yet...
Quick access
Mod profile
Agar-io profile Guest
Guest

No Bio.
`; document.body.append(mod_menu); const styleTag = document.createElement('style'); styleTag.innerHTML = this.style; document.head.append(styleTag); this.getSettings(); this.smallMods(); mod_menu.addEventListener('click', (event) => { if (event.target.closest('.mod_menu_wrapper')) return; mod_menu.style.opacity = 0; setTimeout(() => { mod_menu.style.display = 'none'; }, 300); }); function openModTab(tabId) { const allTabs = document.getElementsByClassName('mod_tab'); const allTabButtons = document.querySelectorAll('.mod_nav_btn'); for (const tab of allTabs) { tab.style.opacity = 0; setTimeout(() => { tab.style.display = 'none'; }, 200); } allTabButtons.forEach((tabBtn) => tabBtn.classList.remove('mod_selected') ); if (tabId === 'mod_gallery') { mods.updateGallery(); } const selectedTab = byId(tabId); setTimeout(() => { selectedTab.style.display = 'flex'; setTimeout(() => { selectedTab.style.opacity = 1; }, 10); }, 200); this.id && this.classList.add('mod_selected'); } window.openModTab = openModTab; document.querySelectorAll('.mod_nav_btn').forEach((tabBtn) => { tabBtn.addEventListener('click', function () { openModTab.call( this, this.id.replace('tab_', 'mod_').replace('_btn', '') ); }); }); const openMenu = document.querySelectorAll( '#clans_and_settings button' )[1]; openMenu.removeAttribute('onclick'); openMenu.addEventListener('click', () => { mod_menu.style.display = 'flex'; setTimeout(() => { mod_menu.style.opacity = 1; }, 10); }); const closeModal = byId('closeBtn'); closeModal.addEventListener('click', () => { mod_menu.style.opacity = 0; setTimeout(() => { mod_menu.style.display = 'none'; }, 300); }); const fontSelectContainer = document.querySelector( '#font-select-container' ); try { const fonts = await this.getGoogleFonts(); const { container: selectElement, selectButton } = this.render_sm_select( 'Select Font', fonts, { width: '200px' }, modSettings.game.font ); const resetFont = document.createElement('button'); resetFont.classList.add('resetButton'); resetFont.addEventListener('click', () => { if (modSettings.game.font === 'Ubuntu') return; modSettings.game.font = 'Ubuntu'; updateStorage(); selectElement.value = 'Ubuntu'; // just change the text of the selectButton selectButton.querySelector('span').textContent = 'Ubuntu'; }); const container = document.createElement('div'); container.classList.add('centerXY', 'g-5'); container.append(resetFont, selectElement); selectElement.addEventListener('change', (e) => { const font = e.detail; const link = document.createElement('link'); link.href = `https://fonts.googleapis.com/css2?family=${font}&display=swap`; link.rel = 'stylesheet'; document.head.appendChild(link); modSettings.game.font = font; updateStorage(); }); fontSelectContainer.replaceWith(container); } catch (e) { fontSelectContainer.replaceWith( "Couldn't load fonts, try again later." ); console.error(e); } if (modSettings.game.font !== 'Ubuntu') { const link = document.createElement('link'); link.href = `https://fonts.googleapis.com/css2?family=${modSettings.game.font}&display=swap`; link.rel = 'stylesheet'; document.head.appendChild(link); } const macroSettings = () => { const allSettingNames = document.querySelectorAll('.setting-card-name'); for (const settingName of Object.values(allSettingNames)) { settingName.addEventListener('click', (event) => { const settingCardWrappers = document.querySelectorAll( '.setting-card-wrapper' ); const currentWrapper = Object.values( settingCardWrappers ).filter( (wrapper) => wrapper.querySelector('.setting-card-name') .textContent === settingName.textContent )[0]; const settingParameters = currentWrapper.querySelector( '.setting-parameters' ); settingParameters.style.display = settingParameters.style.display === 'none' ? 'block' : 'none'; }); } }; macroSettings(); const playTimerToggle = byId('playTimerToggle'); playTimerToggle.addEventListener('change', () => { modSettings.playTimer = playTimerToggle.checked; updateStorage(); }); const mouseTrackerToggle = byId('mouseTrackerToggle'); mouseTrackerToggle.addEventListener('change', () => { modSettings.mouseTracker = mouseTrackerToggle.checked; updateStorage(); }); const macroSpeed = byId('macroSpeed'); const macroSpeedText = byId('macroSpeedText'); macroSpeed.addEventListener('input', () => { modSettings.macros.feedSpeed = macroSpeed.value; macroSpeedText.textContent = `${modSettings.macros.feedSpeed.toString()}ms`; updateStorage(); }); // Reset settings - Mod const resetModSettings = byId('resetModSettings'); resetModSettings.addEventListener('click', () => { if ( confirm( 'Are you sure you want to reset the mod settings? A reload is required.' ) ) { this.removeStorage(storageName); location.reload(); } }); // Reset settings - Game const resetGameSettings = byId('resetGameSettings'); resetGameSettings.addEventListener('click', () => { if ( confirm( 'Are you sure you want to reset the game settings? Your nick and more settings will be lost. A reload is required.' ) ) { window.settings.gameSettings = null; this.removeStorage('settings'); location.reload(); } }); // EventListeners for auth buttons const createAccountBtn = byId('createAccount'); const loginBtn = byId('login'); createAccountBtn.addEventListener('click', () => { this.createSignInWrapper(false); }); loginBtn.addEventListener('click', () => { this.createSignInWrapper(true); }); }, render_sm_select(label, items, style = {}, defaultValue) { const createElement = (tag, styles, text = '') => { const el = document.createElement(tag); el.textContent = text; Object.assign(el.style, styles); return el; }; const defaultcontainerStyles = { position: 'relative', display: 'inline-block', }; const container = createElement('div', { ...defaultcontainerStyles, ...style, }); const selectButton = document.createElement('div'); selectButton.style.cssText = 'background: rgba(0, 0, 0, 0.4); color: #fff; border: 1px solid #A2A2A2; border-radius: 5px; padding: 8px; cursor: pointer; display: flex; justify-content: space-between; align-items: center;'; selectButton.innerHTML = ` ${label} `; if (defaultValue && items.includes(defaultValue)) { selectButton.innerHTML = `${defaultValue} ${ selectButton.innerHTML.split('')[1] }`; } container.appendChild(selectButton); const dropdown = createElement('div', { display: 'none', position: 'absolute', background: '#111', color: '#fafafa', borderRadius: '0 0 10px 10px', padding: '5px 0', zIndex: '999999', maxHeight: '200px', overflowY: 'auto', }); const searchBox = createElement('input', { width: '100%', padding: '5px', marginBottom: '5px', background: '#222', border: 'none', color: '#fff', }); searchBox.placeholder = 'Search...'; dropdown.append(searchBox); items.forEach((item) => { const option = createElement( 'div', { padding: '5px', cursor: 'pointer' }, item ); option.onclick = () => { selectButton.innerHTML = ` ${item} `; dropdown.style.display = 'none'; container.dispatchEvent( new CustomEvent('change', { detail: item }) ); }; option.onmouseover = (e) => (e.target.style.background = '#555'); option.onmouseout = (e) => (e.target.style.background = 'transparent'); dropdown.append(option); }); container.append(dropdown); selectButton.onclick = () => { dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none'; }; document.onclick = (e) => { if (!container.contains(e.target)) dropdown.style.display = 'none'; }; searchBox.addEventListener('input', () => { const filter = searchBox.value.toLowerCase(); Array.from(dropdown.children) .slice(1) .forEach((item) => { item.style.display = item.textContent .toLowerCase() .includes(filter) ? 'block' : 'none'; }); }); return { container, selectButton }; }, setProfile(user) { const img = byId('my-profile-img'); const name = byId('my-profile-name'); const role = byId('my-profile-role'); const bioText = byId('my-profile-bio'); const badges = byId('my-profile-badges'); const bio = user.bio ? user.bio : 'No bio.'; img.src = user.imageURL; name.innerText = user.username; role.innerText = user.role; bioText.innerHTML = bio; badges.innerHTML = user.badges && user.badges.length > 0 ? user.badges .map( (badge) => `${badge}` ) .join('') : 'User has no badges.'; role.classList.add(`${user.role}_role`); }, getSettings() { const mod_qaccess = document.querySelector('#mod_qaccess'); const settingsGrid = document.querySelector( '#settings > .checkbox-grid' ); const settingsNames = settingsGrid.querySelectorAll('label:not([class])'); const inputs = settingsGrid.querySelectorAll('input'); inputs.forEach((checkbox, index) => { if ( checkbox.id === 'showMinimap' || checkbox.id === 'darkTheme' ) return; const modrow = document.createElement('div'); modrow.classList.add('justify-sb', 'p-2'); if ( checkbox.id === 'showChat' || checkbox.id === 'showPosition' || checkbox.id === 'showNames' || checkbox.id === 'showSkins' ) { modrow.style.display = 'none'; } modrow.innerHTML = ` ${settingsNames[index].textContent}
`; mod_qaccess.append(modrow); const cbWrapper = byId(`${checkbox.id}_wrapper`); cbWrapper.appendChild(checkbox); cbWrapper.appendChild( Object.assign(document.createElement('label'), { classList: ['cbx'], htmlFor: checkbox.id, }) ); }); }, themes() { const elements = [ '#menu', '#title', '.top-users', '#left-menu', '.menu-links', '.menu--stats-mode', '#left_ad_block', '#ad_bottom', '.ad-block', '#left_ad_block > .right-menu', '#text-block > .right-menu', '#sigma-pass .connecting__content', '#sigma-status', '.alert', ]; const customTextElements = [ '#challenge-coins .alert-heading', '#sigma-pass h3', '.alert *', ]; let checkInterval; const appliedElements = new Set(); window.themeElements = elements; const themeEditor = document.createElement('div'); themeEditor.classList.add('themeEditor'); themeEditor.style.display = 'none'; themeEditor.innerHTML = `

Theme Editor


Select Theme Type:
preview
`; document.body.append(themeEditor); setTimeout(() => { document .querySelectorAll('.stats-btn__share-btn')[1] .querySelector('rect') .remove(); const themeTypeSelect = byId('theme-type-select'); const colorTab = byId('theme_editor_color'); const gradientTab = byId('theme_editor_gradient'); const imageTab = byId('theme_editor_image'); const gradientAngleDiv = byId('theme-editor-gradient_angle'); themeTypeSelect.addEventListener('change', function () { const selectedOption = themeTypeSelect.value; switch (selectedOption) { case 'Static Color': colorTab.style.display = 'flex'; gradientTab.style.display = 'none'; imageTab.style.display = 'none'; break; case 'Gradient': colorTab.style.display = 'none'; gradientTab.style.display = 'flex'; imageTab.style.display = 'none'; break; case 'Image / Gif': colorTab.style.display = 'none'; gradientTab.style.display = 'none'; imageTab.style.display = 'flex'; break; default: colorTab.style.display = 'flex'; gradientTab.style.display = 'none'; imageTab.style.display = 'none'; } }); const colorInputs = document.querySelectorAll( '#theme_editor_color .colorInput' ); colorInputs.forEach((input) => { input.addEventListener('input', function () { const bgColorInput = byId( 'theme-editor-bgcolorinput' ).value; const textColorInput = byId( 'theme-editor-colorinput' ).value; applyColorTheme(bgColorInput, textColorInput); }); }); const gradientInputs = document.querySelectorAll( '#theme_editor_gradient .colorInput' ); gradientInputs.forEach((input) => { input.addEventListener('input', function () { const gColor1 = byId('theme-editor-gcolor1').value; const gColor2 = byId('theme-editor-g_color').value; const gTextColor = byId('theme-editor-gcolor2').value; const gAngle = byId('g_angle').value; const gradientType = byId('gradient-type').value; applyGradientTheme( gColor1, gColor2, gTextColor, gAngle, gradientType ); }); }); const imageInputs = document.querySelectorAll( '#theme_editor_image .colorInput' ); imageInputs.forEach((input) => { input.addEventListener('input', function () { const imageLinkInput = byId( 'theme-editor-imagelink' ).value; const textColorImageInput = byId( 'theme-editor-textcolorImage' ).value; let img; if (imageLinkInput === '') { img = 'https://i.ibb.co/k6hn4v0/Galaxy-Example.png'; } else { img = imageLinkInput; } applyImageTheme(img, textColorImageInput); }); }); const image_preview = byId('image_preview'); const image_link = byId('theme-editor-imagelink'); let isWriting = false; let timeoutId; image_link.addEventListener('input', () => { if (!isWriting) { isWriting = true; } else { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { const imageLinkInput = image_link.value; const textColorImageInput = byId( 'theme-editor-textcolorImage' ).value; let img; if (imageLinkInput === '') { img = 'https://i.ibb.co/k6hn4v0/Galaxy-Example.png'; } else { img = imageLinkInput; } applyImageTheme(img, textColorImageInput); isWriting = false; }, 1000); }); const gradientTypeSelect = byId('gradient-type'); const angleInput = byId('g_angle'); gradientTypeSelect.addEventListener('change', function () { const selectedType = gradientTypeSelect.value; gradientAngleDiv.style.display = selectedType === 'linear' ? 'flex' : 'none'; const gColor1 = byId('theme-editor-gcolor1').value; const gColor2 = byId('theme-editor-g_color').value; const gTextColor = byId('theme-editor-gcolor2').value; const gAngle = byId('g_angle').value; applyGradientTheme( gColor1, gColor2, gTextColor, gAngle, selectedType ); }); angleInput.addEventListener('input', function () { const gradient_angle_text = byId('gradient_angle_text'); gradient_angle_text.innerText = `Angle (${angleInput.value}deg): `; const gColor1 = byId('theme-editor-gcolor1').value; const gColor2 = byId('theme-editor-g_color').value; const gTextColor = byId('theme-editor-gcolor2').value; const gAngle = byId('g_angle').value; const gradientType = byId('gradient-type').value; applyGradientTheme( gColor1, gColor2, gTextColor, gAngle, gradientType ); }); function applyColorTheme(bgColor, textColor) { const previewDivs = document.querySelectorAll( '#theme_editor_color .themes_preview' ); previewDivs.forEach((previewDiv) => { previewDiv.style.backgroundColor = bgColor; const textSpan = previewDiv.querySelector('span.text'); textSpan.style.color = textColor; }); } function applyGradientTheme( gColor1, gColor2, gTextColor, gAngle, gradientType ) { const previewDivs = document.querySelectorAll( '#theme_editor_gradient .themes_preview' ); previewDivs.forEach((previewDiv) => { const gradient = gradientType === 'linear' ? `linear-gradient(${gAngle}deg, ${gColor1}, ${gColor2})` : `radial-gradient(circle, ${gColor1}, ${gColor2})`; previewDiv.style.background = gradient; const textSpan = previewDiv.querySelector('span.text'); textSpan.style.color = gTextColor; }); } function applyImageTheme(imageLink, textColor) { const previewDivs = document.querySelectorAll( '#theme_editor_image .themes_preview' ); previewDivs.forEach((previewDiv) => { previewDiv.style.backgroundImage = `url('${imageLink}')`; const textSpan = previewDiv.querySelector('span.text'); textSpan.style.color = textColor; }); } const createTheme = byId('createTheme'); createTheme.addEventListener('click', () => { themeEditor.style.display = 'block'; }); const closeThemeEditor = byId('closeThemeEditor'); closeThemeEditor.addEventListener('click', () => { themeEditor.style.display = 'none'; }); let themesDiv = byId('themes'); const saveTheme = (type) => { const name = byId(`${type}ThemeName`).value; if (!name) return; let background, text; if (type === 'color') { background = byId('theme-editor-bgcolorinput').value; text = byId('theme-editor-colorinput').value; } else if (type === 'gradient') { const gColor1 = byId('theme-editor-gcolor1').value; const gColor2 = byId('theme-editor-g_color').value; text = byId('theme-editor-gcolor2').value; const gAngle = byId('g_angle').value; const gradientType = byId('gradient-type').value; background = gradientType === 'linear' ? `linear-gradient(${gAngle}deg, ${gColor1}, ${gColor2})` : `radial-gradient(circle, ${gColor1}, ${gColor2})`; } else if (type === 'image') { background = byId('theme-editor-imagelink').value; text = byId('theme-editor-textcolorImage').value; if (!background) return; } const theme = { name, background, text }; const themeCard = document.createElement('div'); themeCard.classList.add('theme'); themeCard.innerHTML = `
${name}
`; themeCard.addEventListener('click', () => toggleTheme(theme)); themeCard.addEventListener('contextmenu', (ev) => { ev.preventDefault(); if (confirm('Do you want to delete this Theme?')) { themeCard.remove(); const index = modSettings.themes.custom.findIndex(t => t.name === name); if (index !== -1) { modSettings.themes.custom.splice(index, 1); updateStorage(); } } }); themesDiv.appendChild(themeCard); modSettings.themes.custom.push(theme); updateStorage(); themeEditor.style.display = 'none'; themesDiv.scrollTop = themesDiv.scrollHeight; }; byId('saveColorTheme').addEventListener('click', () => saveTheme('color')); byId('saveGradientTheme').addEventListener('click', () => saveTheme('gradient')); byId('saveImageTheme').addEventListener('click', () => saveTheme('image')); }); const b_inner = document.querySelector('.body__inner'); let bodyColorElements = b_inner.querySelectorAll( '.body__inner > :not(.body__inner), #s-skin-select-icon-text' ); const toggleColor = (element, background, text) => { let image = `url("${background}")`; if (background.includes('http')) { element.style.background = image; element.style.backgroundPosition = 'center'; element.style.backgroundSize = 'cover'; element.style.backgroundRepeat = 'no-repeat'; } else { element.style.background = background; element.style.backgroundRepeat = 'no-repeat'; } element.style.color = text; }; const openSVG = document.querySelector( '#clans_and_settings > Button:nth-of-type(2) > svg' ); const openSVGPath = openSVG.querySelector('path'); openSVG.setAttribute('width', '36'); openSVG.setAttribute('height', '36'); const applyThemeToElement = (el, theme) => { if (el.matches('#title')) { el.style.color = theme.text; } else { toggleColor(el, theme.background, theme.text); } appliedElements.add(el); }; const toggleTheme = (theme) => { try { if (checkInterval) clearInterval(checkInterval); appliedElements.clear(); const applyTheme = () => { let allApplied = true; elements.forEach((selector) => { const elements = document.querySelectorAll(selector); elements.forEach((el) => { if (el && !appliedElements.has(el)) { applyThemeToElement(el, theme); allApplied = false; } }); }); customTextElements.forEach((qs) => { document.querySelectorAll(qs).forEach((el) => { if (!appliedElements.has(el)) { el.style.setProperty( 'color', theme.text, 'important' ); appliedElements.add(el); allApplied = false; } }); }); bodyColorElements.forEach((el) => { if (!appliedElements.has(el)) { el.style.color = theme.text; appliedElements.add(el); allApplied = false; } }); if (allApplied) { clearInterval(checkInterval); return; } const isBright = (color) => { if (!color.startsWith('#') || color.length !== 7) return false; const r = parseInt(color.slice(1, 3), 16); const g = parseInt(color.slice(3, 5), 16); const b = parseInt(color.slice(5, 7), 16); return r * 0.299 + g * 0.587 + b * 0.114 > 186; }; openSVGPath.setAttribute( 'fill', isBright(theme.text) ? theme.text : '#222' ); modSettings.themes.current = theme.name; updateStorage(); }; checkInterval = setInterval(applyTheme, 100); } catch (e) { console.error(e); } }; const themes = (window.themes = { defaults: [ { name: 'Dark', background: '#151515', text: '#FFFFFF', }, { name: 'White', background: '#ffffff', text: '#000000', }, { name: 'Transparent', background: 'rgba(0, 0, 0,0)', text: '#FFFFFF', }, ], orderly: [ { name: 'THC', background: 'linear-gradient(160deg, #9BEC7A, #117500)', text: '#000000', }, { name: '4 AM', background: 'linear-gradient(160deg, #8B0AE1, #111)', text: '#FFFFFF', }, { name: 'OTO', background: 'linear-gradient(160deg, #A20000, #050505)', text: '#FFFFFF', }, { name: 'Gaming', background: 'https://i.ibb.co/DwKkQfh/BG-1-lower-quality.jpg', text: '#FFFFFF', }, { name: 'Shapes', background: 'https://i.ibb.co/h8TmVyM/BG-2.png', preview: 'https://czrsd.com/static/sigmod/themes/BG-2.jpg', text: '#FFFFFF', }, { name: 'Blue', background: 'https://i.ibb.co/9yQBfWj/BG-3.png', preview: 'https://czrsd.com/static/sigmod/themes/BG-3.jpg', text: '#FFFFFF', }, { name: 'Blue - 2', background: 'https://i.ibb.co/7RJvNCX/BG-4.png', preview: 'https://czrsd.com/static/sigmod/themes/BG-4.jpg', text: '#FFFFFF', }, { name: 'Purple', background: 'https://i.ibb.co/vxY15Tv/BG-5.png', preview: 'https://czrsd.com/static/sigmod/themes/BG-5.jpg', text: '#FFFFFF', }, { name: 'Orange Blue', background: 'https://i.ibb.co/99nfFBN/BG-6.png', preview: 'https://czrsd.com/static/sigmod/themes/BG-6.jpg', text: '#FFFFFF', }, { name: 'Gradient', background: 'https://i.ibb.co/hWMLwLS/BG-7.png', preview: 'https://czrsd.com/static/sigmod/themes/BG-7.jpg', text: '#FFFFFF', }, { name: 'Sky', background: 'https://i.ibb.co/P4XqDFw/BG-9.png', preview: 'https://czrsd.com/static/sigmod/themes/BG-9.jpg', text: '#000000', }, { name: 'Sunset', background: 'https://i.ibb.co/0BVbYHC/BG-10.png', preview: 'https://czrsd.com/static/sigmod/themes/BG-10.jpg', text: '#FFFFFF', }, { name: 'Galaxy', background: 'https://i.ibb.co/MsssDKP/Galaxy.png', preview: 'https://czrsd.com/static/sigmod/themes/Galaxy.jpg', text: '#FFFFFF', }, { name: 'Planet', background: 'https://i.ibb.co/KLqWM32/Planet.png', preview: 'https://czrsd.com/static/sigmod/themes/Planet.jpg', text: '#FFFFFF', }, { name: 'colorful', background: 'https://i.ibb.co/VqtB3TX/colorful.png', preview: 'https://czrsd.com/static/sigmod/themes/colorful.jpg', text: '#FFFFFF', }, { name: 'Sunset - 2', background: 'https://i.ibb.co/TLp2nvv/Sunset.png', preview: 'https://czrsd.com/static/sigmod/themes/Sunset.jpg', text: '#FFFFFF', }, { name: 'Epic', background: 'https://i.ibb.co/kcv4tvn/Epic.png', preview: 'https://czrsd.com/static/sigmod/themes/Epic.jpg', text: '#FFFFFF', }, { name: 'Galaxy - 2', background: 'https://i.ibb.co/smRs6V0/galaxy.png', preview: 'https://czrsd.com/static/sigmod/themes/galaxy2.jpg', text: '#FFFFFF', }, { name: 'Cloudy', background: 'https://i.ibb.co/MCW7Bcd/cloudy.png', preview: 'https://czrsd.com/static/sigmod/themes/cloudy.jpg', text: '#000000', }, ], }); function createThemeCard(theme) { const themeCard = document.createElement('div'); themeCard.classList.add('theme'); let themeBG; if (theme.background.includes('http')) { themeBG = `background: url(${ theme.preview || theme.background })`; } else { themeBG = `background: ${theme.background}`; } themeCard.innerHTML = `
${theme.name}
`; themeCard.addEventListener('click', () => { toggleTheme(theme); }); if (modSettings.themes.custom.includes(theme)) { themeCard.addEventListener( 'contextmenu', (ev) => { ev.preventDefault(); if (confirm('Do you want to delete this Theme?')) { themeCard.remove(); const themeIndex = modSettings.themes.custom.findIndex( (addedTheme) => addedTheme.name === theme.name ); if (themeIndex !== -1) { modSettings.themes.custom.splice( themeIndex, 1 ); updateStorage(); } } }, false ); } return themeCard; } const themesContainer = byId('themes'); themes.defaults.forEach((theme) => { const themeCard = createThemeCard(theme); themesContainer.append(themeCard); }); const orderlyThemes = [ ...themes.orderly, ...modSettings.themes.custom, ]; orderlyThemes.sort((a, b) => a.name.localeCompare(b.name)); orderlyThemes.forEach((theme) => { const themeCard = createThemeCard(theme); themesContainer.appendChild(themeCard); }); const savedTheme = modSettings.themes.current; if (savedTheme) { let selectedTheme; selectedTheme = themes.defaults.find( (theme) => theme.name === savedTheme ); if (!selectedTheme) { selectedTheme = themes.orderly.find( (theme) => theme.name === savedTheme ) || modSettings.themes.custom.find( (theme) => theme.name === savedTheme ); } if (selectedTheme) { toggleTheme(selectedTheme); } } const inputBorderRadius = byId('theme-inputBorderRadius'); const menuBorderRadius = byId('theme-menuBorderRadius'); const inputBorder = byId('theme-inputBorder'); function setCSS(key, value, targets, property) { modSettings.themes[key] = value; targets.forEach((target) => { document .querySelectorAll(target) .forEach((el) => (el.style[property] = value)); }); updateStorage(); } inputBorderRadius.value = modSettings.themes.inputBorderRadius ? modSettings.themes.inputBorderRadius.replace('px', '') : '4'; menuBorderRadius.value = modSettings.themes.menuBorderRadius ? modSettings.themes.menuBorderRadius.replace('px', '') : '15'; inputBorder.checked = modSettings.themes.inputBorder ? modSettings.themes.inputBorder === '1px' : '1px'; setCSS( 'inputBorderRadius', `${inputBorderRadius.value}px`, ['.form-control'], 'borderRadius' ); setCSS( 'menuBorderRadius', `${menuBorderRadius.value}px`, [...elements, '.text-block'], 'borderRadius' ); setCSS( 'inputBorder', inputBorder.checked ? '1px' : '0px', ['.form-control'], 'borderWidth' ); inputBorderRadius.addEventListener('input', () => setCSS( 'inputBorderRadius', `${inputBorderRadius.value}px`, ['.form-control'], 'borderRadius' ) ); menuBorderRadius.addEventListener('input', () => setCSS( 'menuBorderRadius', `${menuBorderRadius.value}px`, [...elements, '.text-block'], 'borderRadius' ) ); inputBorder.addEventListener('input', () => setCSS( 'inputBorder', inputBorder.checked ? '1px' : '0px', ['.form-control'], 'borderWidth' ) ); const reset_input_radius = document.getElementById('reset_input_radius'); const reset_menu_radius = document.getElementById('reset_menu_radius'); reset_input_radius.addEventListener('click', () => { const defaultBorderRadius = 4; inputBorderRadius.value = defaultBorderRadius; setCSS( 'inputBorderRadius', `${defaultBorderRadius}px`, ['.form-control'], 'borderRadius' ) }); reset_menu_radius.addEventListener('click', () => { const defaultBorderRadius = 15; menuBorderRadius.value = defaultBorderRadius; setCSS( 'menuBorderRadius', `${defaultBorderRadius}px`, [...elements, '.text-block'], 'borderRadius' ) }); const hideDiscordBtns = document.getElementById('hideDiscordBtns'); const dclinkdiv = document.getElementById('dclinkdiv'); hideDiscordBtns.addEventListener('change', () => { if (hideDiscordBtns.checked) { dclinkdiv.classList.add('hidden_full'); modSettings.themes.hideDiscordBtns = true; } else { dclinkdiv.classList.remove('hidden_full'); modSettings.themes.hideDiscordBtns = false; } updateStorage(); }); if (modSettings.themes.hideDiscordBtns) { dclinkdiv.classList.add('hidden_full'); hideDiscordBtns.checked = true; } const hideLangs = document.getElementById('hideLangs'); const langsDiv = document.querySelector('.ch-lang'); hideLangs.addEventListener('change', () => { if (hideLangs.checked) { langsDiv.classList.add('hidden_full'); modSettings.themes.hideLangs = true; } else { langsDiv.classList.remove('hidden_full'); modSettings.themes.hideLangs = false; } updateStorage(); }); if (modSettings.themes.hideLangs && langsDiv) { langsDiv.classList.add('hidden_full'); hideLangs.checked = true; } const popup = byId('shop-popup'); const removeShopPopup = byId('removeShopPopup'); removeShopPopup.addEventListener('change', () => { if (removeShopPopup.checked) { popup.classList.add('hidden_full'); modSettings.settings.removeShopPopup = true; } else { popup.classList.remove('hidden_full'); modSettings.settings.removeShopPopup = false; } updateStorage(); }); if (modSettings.settings.removeShopPopup) { popup.classList.add('hidden_full'); removeShopPopup.checked = true; } }, isAuthenticated() { const name = byId('profile-name'); if (name && (name !== 'Guest' || name !== 'undefined')) return true; else return false; }, chat() { const chatDiv = document.createElement('div'); chatDiv.classList.add('modChat'); chatDiv.innerHTML = `
`; document.body.append(chatDiv); const chatContainer = byId('mod-messages'); const scrollDownButton = byId('scroll-down-btn'); chatContainer.addEventListener('scroll', () => { if ( chatContainer.scrollHeight - chatContainer.scrollTop > 300 ) { scrollDownButton.style.display = 'block'; } if ( chatContainer.scrollHeight - chatContainer.scrollTop < 299 && scrollDownButton.style.display === 'block' ) { scrollDownButton.style.display = 'none'; } }); scrollDownButton.addEventListener('click', () => { chatContainer.scrollTop = chatContainer.scrollHeight; }); const main = byId('mainchat'); const party = byId('partychat'); main.addEventListener('click', () => { if (!window.gameSettings.user) { const chatSendInput = document.querySelector('#chatSendInput'); if (!chatSendInput) return; chatSendInput.placeholder = 'Login to use the chat'; chatSendInput.disabled = true; } if (modSettings.chat.showClientChat) { byId('mod-messages').innerHTML = ''; modSettings.chat.showClientChat = false; updateStorage(); } }); party.addEventListener('click', () => { const chatSendInput = document.querySelector('#chatSendInput'); if (chatSendInput) { chatSendInput.placeholder = 'message...'; chatSendInput.disabled = false; } if (!modSettings.chat.showClientChat) { modSettings.chat.showClientChat = true; updateStorage(); } const modMessages = byId('mod-messages'); if (!modSettings.settings.tag) { modMessages.innerHTML = `
[SERVER]: You need to be in a tag to use the SigMod party chat.
`; } else { modMessages.innerHTML = `
[SERVER]: Welcome to the SigMod party chat!
`; } }); if (modSettings.chat.showClientChat) { const chatSendInput = document.querySelector('#chatSendInput'); if (!chatSendInput) return; chatSendInput.placeholder = 'message...'; chatSendInput.disabled = false; setTimeout(() => { const modMessages = byId('mod-messages'); modMessages.innerHTML = `
[SERVER]: Welcome to the SigMod party chat!
`; }, 1000); } const text = byId('chatSendInput'); const send = byId('sendButton'); send.addEventListener('click', () => { let val = text.value; if (val === '') return; if (modSettings.chat.showClientChat) { if (client?.ws?.readyState !== 1) return; // party chat message client.send({ type: 'chat-message', content: { message: val, }, }); } else { // Sigmally chat message: split text into parts if message is longer than 15 characters if (val.length > 15) { const parts = []; let currentPart = ''; // split the input value into individual words val.split(' ').forEach((word) => { if (currentPart.length + word.length + 1 <= 15) { currentPart += (currentPart ? ' ' : '') + word; } else { parts.push(currentPart); currentPart = word; } }); if (currentPart) { parts.push(currentPart); } let index = 0; const sendPart = () => { if (index < parts.length) { window.sendChat(parts[index]); index++; setTimeout(sendPart, 1000); // 1s cooldown from sigmally } }; sendPart(); } else { window.sendChat(val); } } text.value = ''; text.blur(); }); this.chatSettings(); this.emojiMenu(); this.getBlockedChatData(); const chatSettingsContainer = document.querySelector( '.chatSettingsContainer' ); const emojisContainer = document.querySelector('.emojisContainer'); byId('openChatSettings').addEventListener('click', () => { if (chatSettingsContainer.classList.contains('hidden_full')) { chatSettingsContainer.classList.remove('hidden_full'); emojisContainer.classList.add('hidden_full'); } else { chatSettingsContainer.classList.add('hidden_full'); } }); const scrollUpButton = byId('scroll-down-btn'); let focused = false; let typed = false; document.addEventListener('keydown', (e) => { if (e.key === 'Enter' && text.value.length > 0) { send.click(); focused = false; scrollUpButton.click(); } else if (e.key === 'Enter') { if ( document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' ) return; focused ? text.blur() : text.focus(); focused = !focused; } }); text.addEventListener('input', () => { typed = text.value.length > 1; }); text.addEventListener('blur', () => { focused = false; }); text.addEventListener('keydown', (e) => { const key = e.key.toLowerCase(); if (key === 'w') { e.stopPropagation(); } if (key === ' ') { e.stopPropagation(); } }); // switch to compact chat const chatElements = [ '.modChat', '.emojisContainer', '.chatSettingsContainer', ]; const emojiBtn = byId('openEmojiMenu'); const compactChat = byId('compactChat'); compactChat.addEventListener('change', () => { compactChat.checked ? compactMode() : defaultMode(); }); function compactMode() { chatElements.forEach((querySelector) => { const el = document.querySelector(querySelector); if (el) { el.classList.add('mod-compact'); } }); emojiBtn.style.display = 'none'; modSettings.chat.compact = true; updateStorage(); } function defaultMode() { chatElements.forEach((querySelector) => { const el = document.querySelector(querySelector); if (el) { el.classList.remove('mod-compact'); } }); emojiBtn.style.display = 'flex'; modSettings.chat.compact = false; updateStorage(); } if (modSettings.chat.compact) compactMode(); }, spamMessage(name, message) { return ( this.blockedChatData.names.some(n => name.toLowerCase().includes(n.toLowerCase())) || this.blockedChatData.messages.some(m => message.toLowerCase().includes(m.toLowerCase())) ); }, updateChat(data) { const chatContainer = byId('mod-messages'); const isScrolledToBottom = chatContainer.scrollHeight - chatContainer.scrollTop <= chatContainer.clientHeight + 1; const isNearBottom = chatContainer.scrollHeight - chatContainer.scrollTop - 200 <= chatContainer.clientHeight; let { name, message, time = '' } = data; name = noXSS(name); time = data.time !== null ? prettyTime.am_pm(data.time) : ''; const color = this.friend_names.has(name) ? this.friends_settings.highlight_color : data.color || '#ffffff'; const glow = this.friend_names.has(name) && this.friends_settings.highlight_friends ? `text-shadow: 0 1px 3px ${color}` : ''; const id = rdmString(12); const chatMessage = document.createElement('div'); chatMessage.classList.add('message'); chatMessage.innerHTML = `
${name} :
${time} `; chatMessage.querySelector('.chatMessage-text').innerHTML = message; chatContainer.append(chatMessage); if (isScrolledToBottom || isNearBottom) chatContainer.scrollTop = chatContainer.scrollHeight; if (name === this.nick) return; const nameEl = byId(id); nameEl.addEventListener('mousedown', (e) => this.handleContextMenu(e, name) ); nameEl.addEventListener('contextmenu', (e) => { e.preventDefault(); e.stopPropagation(); }); if (++this.renderedMessages > this.maxChatMessages) { chatContainer.removeChild(chatContainer.firstChild); this.renderedMessages--; } }, handleContextMenu(e, name) { if (this.onContext || e.button !== 2) return; const contextMenu = document.createElement('div'); contextMenu.classList.add('chat-context'); contextMenu.innerHTML = ` ${name} `; Object.assign(contextMenu.style, { top: `${e.clientY - 80}px`, left: `${e.clientX}px`, }); document.body.appendChild(contextMenu); this.onContext = true; byId('muteButton').addEventListener('click', () => { const confirmMsg = name === 'Spectator' ? 'Are you sure you want to mute all spectators until you refresh the page?' : `Are you sure you want to mute '${name}' until you refresh the page?`; if (confirm(confirmMsg)) { this.muteUser(name); contextMenu.remove(); } }); document.addEventListener('click', (event) => { if (!contextMenu.contains(event.target)) { this.onContext = false; contextMenu.remove(); } }); }, muteUser(name) { this.mutedUsers.push(name); const msgNames = document.querySelectorAll('.message_name'); msgNames.forEach((msgName) => { if (msgName.innerHTML == name) { const msgParent = msgName.closest('.message'); msgParent.remove(); } }); }, async getGoogleFonts() { const fontFamilies = await ( await fetch(this.appRoutes.fonts) ).json(); return fontFamilies; }, async getEmojis() { const response = await fetch( 'https://czrsd.com/static/sigmod/emojis.json' ); return await response.json(); }, emojiMenu() { const updateEmojis = (searchTerm = '') => { const emojisContainer = document.querySelector('.emojisContainer'); const categoriesContainer = emojisContainer.querySelector('#categories'); categoriesContainer.innerHTML = ''; window.emojis.forEach((emojiData) => { const { emoji, description, category, tags } = emojiData; if ( !searchTerm || tags.some((tag) => tag.includes(searchTerm.toLowerCase()) ) ) { let categoryId = category .replace(/\s+/g, '-') .replace('&', 'and') .toLowerCase(); let categoryDiv = categoriesContainer.querySelector( `#${categoryId}` ); if (!categoryDiv) { categoryDiv = document.createElement('div'); categoryDiv.id = categoryId; categoryDiv.classList.add('category'); categoryDiv.innerHTML = `${category}
`; categoriesContainer.appendChild(categoryDiv); } const emojiContainer = categoryDiv.querySelector('.emojiContainer'); const emojiDiv = document.createElement('div'); emojiDiv.classList.add('emoji'); emojiDiv.innerHTML = emoji; emojiDiv.title = `${emoji} - ${description}`; emojiDiv.addEventListener('click', () => { const chatInput = document.querySelector('#chatSendInput'); chatInput.value += emoji; }); emojiContainer.appendChild(emojiDiv); } }); }; const emojisContainer = document.createElement('div'); emojisContainer.classList.add( 'chatAddedContainer', 'emojisContainer', 'hidden_full' ); emojisContainer.innerHTML = `
`; const chatInput = emojisContainer.querySelector('#searchEmoji'); chatInput.addEventListener('input', (event) => { const searchTerm = event.target.value.toLowerCase(); updateEmojis(searchTerm); }); document.body.append(emojisContainer); const chatSettingsContainer = document.querySelector( '.chatSettingsContainer' ); byId('openEmojiMenu').addEventListener('click', () => { if (!window.emojis) { this.getEmojis().then((emojis) => { window.emojis = emojis; updateEmojis(); }); } if (emojisContainer.classList.contains('hidden_full')) { emojisContainer.classList.remove('hidden_full'); chatSettingsContainer.classList.add('hidden_full'); } else { emojisContainer.classList.add('hidden_full'); } }); }, chatSettings() { const menu = document.createElement('div'); menu.classList.add( 'chatAddedContainer', 'chatSettingsContainer', 'scroll', 'hidden_full' ); menu.innerHTML = `
Keybindings
Location
Show / Hide
General
Time
Name colors
Party / Main
Blur Tag
Location text
Style
Compact chat
Text
Background
Theme
`; document.body.append(menu); const infoIcon = document.querySelector('.infoIcon'); const modInfoPopup = document.querySelector('.modInfoPopup'); let popupOpen = false; infoIcon.addEventListener('click', (event) => { event.stopPropagation(); modInfoPopup.style.display = popupOpen ? 'none' : 'block'; popupOpen = !popupOpen; }); document.addEventListener('click', (event) => { if (popupOpen && !modInfoPopup.contains(event.target)) { modInfoPopup.style.display = 'none'; popupOpen = false; } }); const showChatTime = document.querySelector('#showChatTime'); const showNameColors = document.querySelector('#showNameColors'); showChatTime.addEventListener('change', () => { const timeElements = document.querySelectorAll('.time'); if (showChatTime.checked) { modSettings.chat.showTime = true; updateStorage(); } else { modSettings.chat.showTime = false; if (timeElements) { timeElements.forEach((el) => (el.innerHTML = '')); } updateStorage(); } }); showNameColors.addEventListener('change', () => { const message_names = document.querySelectorAll('.message_name'); if (showNameColors.checked) { modSettings.chat.showNameColors = true; updateStorage(); } else { modSettings.chat.showNameColors = false; if (message_names) { message_names.forEach( (el) => (el.style.color = '#fafafa') ); } updateStorage(); } }); // remove old rgba val if (modSettings.chat.bgColor.includes('rgba')) { modSettings.chat.bgColor = RgbaToHex(modSettings.chat.bgColor); } const modChat = document.querySelector('.modChat'); modChat.style.background = modSettings.chat.bgColor; const showPartyMain = document.querySelector('#showPartyMain'); const chatHeader = document.querySelector('.modchat-chatbuttons'); const changeButtonsState = (show) => { chatHeader.style.display = show ? 'flex' : 'none'; modChat.style.maxHeight = show ? '285px' : '250px'; modChat.style.minHeight = show ? '285px' : '250px'; const modChatInner = document.querySelector('.modChat__inner'); modChatInner.style.maxHeight = show ? '265px' : '230px'; modChatInner.style.minHeight = show ? '265px' : '230px'; }; showPartyMain.addEventListener('change', () => { const show = showPartyMain.checked; modSettings.chat.showChatButtons = show; changeButtonsState(show); updateStorage(); }); showPartyMain.checked = modSettings.chat.showChatButtons; changeButtonsState(modSettings.chat.showChatButtons); setTimeout(() => { const blurTag = byId('blurTag'); const tagText = document.querySelector('.tagText'); const tagElement = document.querySelector('#tag'); blurTag.addEventListener('change', () => { const state = blurTag.checked; state ? (tagText.classList.add('blur'), tagElement.classList.add('blur')) : (tagText.classList.remove('blur'), tagElement.classList.remove('blur')); modSettings.chat.blurTag = state; updateStorage(); }); blurTag.checked = modSettings.chat.blurTag; if (modSettings.chat.blurTag) { tagText.classList.add('blur'); tagElement.classList.add('blur'); } }); const locationText = byId('locationText'); locationText.addEventListener('input', (e) => { e.stopPropagation(); modSettings.chat.locationText = locationText.value; }); locationText.value = modSettings.chat.locationText || '{pos}'; }, toggleChat() { const modChat = document.querySelector('.modChat'); const modChatAdded = document.querySelectorAll( '.chatAddedContainer' ); const isModChatHidden = modChat.style.display === 'none' || getComputedStyle(modChat).display === 'none'; if (isModChatHidden) { modChat.style.opacity = 0; modChat.style.display = 'flex'; setTimeout(() => { modChat.style.opacity = 1; }, 10); } else { modChat.style.opacity = 0; modChatAdded.forEach((container) => container.classList.add('hidden_full') ); setTimeout(() => { modChat.style.display = 'none'; }, 300); } }, smallMods() { // fix auth for tournament page if (location.pathname.includes('tournament')) { const tempDiv = document.createElement('div'); tempDiv.classList.add('top-winners__list'); document.body.appendChild(tempDiv); const observer = new MutationObserver(() => { if (tempDiv.children.length > 0) { tempDiv.remove(); observer.disconnect(); } }) observer.observe(tempDiv, { childList: true }); } const modAlert_overlay = document.createElement('div'); modAlert_overlay.classList.add('alert_overlay'); modAlert_overlay.id = 'modAlert_overlay'; document.body.append(modAlert_overlay); const autoRespawn = byId('autoRespawn'); if (modSettings.settings.autoRespawn) { autoRespawn.checked = true; } autoRespawn.addEventListener('change', () => { modSettings.settings.autoRespawn = autoRespawn.checked; updateStorage(); }); const auto = byId('autoClaimCoins'); auto.addEventListener('change', () => { const checked = auto.checked; modSettings.settings.autoClaimCoins = !!checked; updateStorage(); }); if (modSettings.settings.autoClaimCoins) { auto.checked = true; } const showChallenges = byId('showChallenges'); showChallenges.addEventListener('change', () => { if (showChallenges.checked) { modSettings.showChallenges = true; } else { modSettings.showChallenges = false; } updateStorage(); }); if (modSettings.showChallenges) { auto.checked = true; } const gameTitle = byId('title'); const newTitle = document.createElement('div'); newTitle.classList.add('sigmod-title'); newTitle.innerHTML = `

Sigmally

Mod by Cursed `; gameTitle.replaceWith(newTitle); const nickName = byId('nick'); nickName.maxLength = 50; nickName.type = 'text'; function updNick() { const nick = nickName.value; mods.nick = nick; const welcome = byId('welcomeUser'); if (welcome) { welcome.innerHTML = `Welcome ${ mods.nick || 'Unnamed' }, to the SigMod Client!`; } } nickName.addEventListener('input', () => { updNick(); }); updNick(); // Better grammar in the descriptions of the challenges setTimeout(() => { window.shopLocales.challenge_tab.tasks = { eaten: 'Eat %n food in a game.', xp: 'Get %n XP in a game.', alive: 'Stay alive for %n minutes in a game.', pos: 'Reach top %n on leaderboard.', }; }, 1000); const topusersInner = document.querySelector('.top-users__inner'); topusersInner.classList.add('scroll'); topusersInner.style.border = 'none'; byId('signOutBtn').addEventListener('click', () => { window.gameSettings.user = null; }); const mode = byId('gamemode'); mode.addEventListener('change', () => { client.send({ type: 'server-changed', content: getGameMode(), }); window.gameSettings.isPlaying = false; const modMessages = document.querySelector('#mod-messages'); if (modMessages) { modMessages.innerHTML = ''; } }); // redirect to owned skins instead of free skins const ot = Element.prototype.openTab; Element.prototype.openTab = function (tab) { if (!tab === 'skins') return; setTimeout(() => { Element.prototype.changeTab('owned'); }, 100); ot.apply(this, arguments); }; document.addEventListener('mousemove', (e) => { this.mouseX = e.clientX + window.pageXOffset; this.mouseY = e.clientY + window.pageYOffset; const mouseTracker = document.querySelector('.mouseTracker'); if (!mouseTracker) return; mouseTracker.innerText = `X: ${this.mouseX}; Y: ${this.mouseY}`; }); if (location.search.includes('password')) { const passwordField = byId('password'); if (passwordField) passwordField.style.display = 'none'; const password = new URLSearchParams(location.search) .get('password') ?.split('/')[0] || ''; passwordField.value = password; // sigfixes should know the password when multiboxing if (window.sigfix) return; const playBtn = byId('play-btn'); playBtn.addEventListener('click', (e) => { const waitForConnection = () => new Promise((res) => { if (client?.ws?.readyState === 1) return res(null); const i = setInterval( () => client?.ws?.readyState === 1 && (clearInterval(i), res(null)), 50 ); }); waitForConnection().then(async () => { await wait(500); mods.sendPlay(password); const interval = setInterval(() => { const errormodal = byId('errormodal'); if (errormodal?.style.display !== 'none') errormodal.style.display = 'none'; }); setTimeout(() => clearInterval(interval), 1000); }); }); } }, removeStorage(storage) { localStorage.removeItem(storage); }, credits() { console.log( `%c ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Žā–‘ā–ˆā–€ā–€ā–€ā–ˆ ā–€ā–ˆā–€ ā–‘ā–ˆā–€ā–€ā–ˆ ā–‘ā–ˆā–€ā–„ā–€ā–ˆ ā–‘ā–ˆā–€ā–€ā–€ā–ˆ ā–‘ā–ˆā–€ā–€ā–„ ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Žā”€ā–€ā–€ā–€ā–„ā–„ ā–‘ā–ˆā”€ ā–‘ā–ˆā”€ā–„ā–„ ā–‘ā–ˆā–‘ā–ˆā–‘ā–ˆ ā–‘ā–ˆā”€ā”€ā–‘ā–ˆ ā–‘ā–ˆā”€ā–‘ā–ˆ ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž V${version} ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Žā–‘ā–ˆā–„ā–„ā–„ā–ˆ ā–„ā–ˆā–„ ā–‘ā–ˆā–„ā–„ā–ˆ ā–‘ā–ˆā”€ā”€ā–‘ā–ˆ ā–‘ā–ˆā–„ā–„ā–„ā–ˆ ā–‘ā–ˆā–„ā–„ā–€ ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–‘ā–ˆā–ˆā•—ā–‘ā–‘ā–‘ā–ˆā–ˆā•—ā€ƒā€ƒā–‘ā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–‘ā–ˆā–ˆā•—ā–‘ā–‘ā–‘ā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–‘ā–‘ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–‘ ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā•šā–ˆā–ˆā•—ā–‘ā–ˆā–ˆā•”ā•ā€ƒā€ƒā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•‘ā–‘ā–‘ā–‘ā–ˆā–ˆā•‘ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā•ā•ā•ā–ˆā–ˆā•”ā•ā•ā•ā•ā•ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•— ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•¦ā•ā–‘ā•šā–ˆā–ˆā–ˆā–ˆā•”ā•ā–‘ā€ƒā€ƒā–ˆā–ˆā•‘ā–‘ā–‘ā•šā•ā•ā–ˆā–ˆā•‘ā–‘ā–‘ā–‘ā–ˆā–ˆā•‘ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā•šā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–‘ā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–‘ā–‘ā–ˆā–ˆā•‘ā–‘ā–‘ā–ˆā–ˆā•‘ ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–‘ā–‘ā•šā–ˆā–ˆā•”ā•ā–‘ā–‘ā€ƒā€ƒā–ˆā–ˆā•‘ā–‘ā–‘ā–ˆā–ˆā•—ā–ˆā–ˆā•‘ā–‘ā–‘ā–‘ā–ˆā–ˆā•‘ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–‘ā•šā•ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā•ā–‘ā–‘ā–ˆā–ˆā•‘ā–‘ā–‘ā–ˆā–ˆā•‘ ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•¦ā•ā–‘ā–‘ā–‘ā–ˆā–ˆā•‘ā–‘ā–‘ā–‘ā€ƒā€ƒā•šā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā•šā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā•‘ā–‘ā–‘ā–ˆā–ˆā•‘ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā• ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā•šā•ā•ā•ā•ā•ā•ā–‘ā–‘ā–‘ā–‘ā•šā•ā•ā–‘ā–‘ā–‘ā€ƒā€ƒā–‘ā•šā•ā•ā•ā•ā•ā–‘ā–‘ā•šā•ā•ā•ā•ā•ā•ā–‘ā•šā•ā•ā–‘ā–‘ā•šā•ā•ā•šā•ā•ā•ā•ā•ā•ā–‘ā•šā•ā•ā•ā•ā•ā•ā•ā•šā•ā•ā•ā•ā•ā•ā–‘ ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž ā€Ž `, 'background-color: black; color: green' ); }, handleAlert(data) { const { title, description, enabled, password } = data; if (location.pathname.includes('tournament') || client.updateAvailable) return; const hideAlert = Number(localStorage.getItem('hide-alert')); // Don't show alert if it has been closed the past 3 hours if (!enabled || (hideAlert && (Date.now() - hideAlert < 3 * 60 * 60 * 1000))) { byId('scrim_alert')?.remove(); return; } localStorage.removeItem('hide-alert'); const modAlert = document.createElement('div'); modAlert.id = 'scrim_alert'; modAlert.classList.add('modAlert'); modAlert.innerHTML = `
${title}
${description}
`; document.body.append(modAlert); const observer = new MutationObserver(() => { modAlert.style.display = menuClosed() ? 'none' : 'flex'; }); observer.observe(document.body, { attributes: true, childList: true, subtree: true, }); const joinButton = byId('join'); joinButton.addEventListener('click', () => { location.href = `https://one.sigmally.com/tournament?password=${password}`; }); const close = byId('close_scrim_alert'); close.addEventListener('click', () => { modAlert.remove(); // make it not that annoying localStorage.setItem('hide-alert', Date.now()); }); }, saveNames() { let savedNames = modSettings.settings.savedNames; let savedNamesOutput = byId('savedNames'); let saveNameBtn = byId('saveName'); let saveNameInput = byId('saveNameValue'); const createNameDiv = (name) => { let nameDiv = document.createElement('div'); nameDiv.classList.add('NameDiv'); let nameLabel = document.createElement('label'); nameLabel.classList.add('NameLabel'); nameLabel.innerText = name; let delName = document.createElement('button'); delName.innerText = 'X'; delName.classList.add('delName'); nameDiv.addEventListener('click', () => { const name = nameLabel.innerText; navigator.clipboard.writeText(name).then(() => { this.modAlert( `Added the name '${name}' to your clipboard!`, 'success' ); }); }); delName.addEventListener('click', () => { if ( confirm( "Are you sure you want to delete the name '" + nameLabel.innerText + "'?" ) ) { nameDiv.remove(); savedNames = savedNames.filter( (n) => n !== nameLabel.innerText ); modSettings.settings.savedNames = savedNames; updateStorage(); } }); nameDiv.appendChild(nameLabel); nameDiv.appendChild(delName); return nameDiv; }; saveNameBtn.addEventListener('click', () => { if (saveNameInput.value === '') return; setTimeout(() => { saveNameInput.value = ''; }, 10); if (savedNames.includes(saveNameInput.value)) { return; } let nameDiv = createNameDiv(saveNameInput.value); savedNamesOutput.appendChild(nameDiv); savedNames.push(saveNameInput.value); modSettings.settings.savedNames = savedNames; updateStorage(); }); if (savedNames.length > 0) { savedNames.forEach((name) => { let nameDiv = createNameDiv(name); savedNamesOutput.appendChild(nameDiv); }); } }, initStats() { // initialize player stats const statElements = [ 'stat-time-played', 'stat-highest-mass', 'stat-total-deaths', 'stat-total-mass', ]; this.storage = localStorage.getItem('game-stats'); if (!this.storage) { this.storage = { 'time-played': 0, // seconds 'highest-mass': 0, 'total-deaths': 0, 'total-mass': 0, }; localStorage.setItem( 'game-stats', JSON.stringify(this.storage) ); } else { this.storage = JSON.parse(this.storage); } statElements.forEach((rawStat) => { const stat = rawStat.replace('stat-', ''); const value = this.storage[stat]; this.updateStatElm(rawStat, value); }); this.session.bind(this)(); }, updateStat(key, value) { this.storage[key] = value; localStorage.setItem('game-stats', JSON.stringify(this.storage)); this.updateStatElm(key, value); }, updateStatElm(stat, value) { const element = byId(stat); if (element) { if (stat === 'stat-time-played') { const hours = Math.floor(value / 3600); const minutes = Math.floor((value % 3600) / 60); const formattedTime = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`; element.innerHTML = formattedTime; } else { const formattedValue = stat === 'stat-highest-mass' || stat === 'stat-total-mass' ? value > 999 ? `${(value / 1000).toFixed(1)}k` : value.toString() : value.toString(); element.innerHTML = formattedValue; } } }, session() { let playingInterval; let minPlaying = 0; let isPlaying = window.gameSettings.isPlaying; const playBtn = byId('play-btn'); let a = null; playBtn.addEventListener('click', async () => { if (isPlaying) return; while (!window.gameSettings.isPlaying) { await new Promise((resolve) => setTimeout(resolve, 200)); } isPlaying = true; let timer = null; if (modSettings.playTimer) { timer = document.createElement('span'); timer.classList.add('playTimer'); timer.innerText = '0m0s played'; document.body.append(timer); } // mouse tracker in session method so it's only visible when playing if (modSettings.mouseTracker) { const mouse = document.createElement('span'); mouse.classList.add('mouseTracker'); mouse.innerText = 'X: 0; Y: 0'; document.body.append(mouse); if (!modSettings.playTimer) { mouse.style.top = '128px'; } } let count = 0; playingInterval = setInterval(() => { count++; this.storage['time-played']++; if (count % 60 === 0) { minPlaying++; } this.updateStat('time-played', this.storage['time-played']); if (modSettings.playTimer) { this.updateTimeStat(timer, count); } }, 1000); }); setInterval(() => { if (menuClosed() && byId('overlays').style.display !== 'none') { byId('overlays').style.display = 'none'; } if (isDead() && !dead) { clearInterval(playingInterval); dead = true; const playTimer = document.querySelector('.playTimer'); if (playTimer) playTimer.remove(); const mouseTracker = document.querySelector('.mouseTracker'); if (mouseTracker) mouseTracker.remove(); const score = parseFloat(byId('highest_mass').innerText); const highest = this.storage['highest-mass']; if (score > highest) { this.storage['highest-mass'] = score; this.updateStat( 'highest-mass', this.storage['highest-mass'] ); } this.storage['total-deaths']++; this.updateStat( 'total-deaths', this.storage['total-deaths'] ); this.storage['total-mass'] += score; this.updateStat('total-mass', this.storage['total-mass']); isPlaying = window.gameSettings.isPlaying = false; if (this.lastOneStanding) { client.send({ type: 'result', content: null, }); playBtn.disabled = true; } if (modSettings.showChallenges && window.gameSettings.user) this.showChallenges(); } else if (!isDead()) { dead = false; const challengesDeathscreen = document.querySelector( '.challenges_deathscreen' ); if (challengesDeathscreen) challengesDeathscreen.remove(); } }); }, updateTimeStat(el, seconds) { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; const timeString = `${minutes}m${remainingSeconds}s`; el.innerText = `${timeString} played`; }, async showChallenges() { const challengeData = await ( await fetch( `https://sigmally.com/api/user/challenge/${window.gameSettings.user.email}` ) ).json(); if (challengeData.status !== 'success') return; const shopLocales = window.shopLocales; let challengesCompleted = 0; const allChallenges = challengeData.data; allChallenges.forEach(({ status }) => { if (status) challengesCompleted++; }); let challenges; if (challengesCompleted === allChallenges.length) { challenges = `
All challenges completed.
`; } else { challenges = allChallenges .filter(({ status }) => !status) .map(({ task, best, status, ready, goal }) => { const desc = shopLocales.challenge_tab.tasks[ task ].replace('%n', task === 'alive' ? goal / 60 : goal); const btn = ready ? `` : `
${ shopLocales.challenge_tab.result }${Math.round(best)}${ task === 'alive' ? 's' : '' }
`; return `
${desc}
${btn}
`; }) .join(''); } const modal = document.createElement('div'); modal.classList.add('challenges_deathscreen'); modal.innerHTML = ` Daily challenges
${challenges}
New challenges in 0h 0m 0s `; const toggleColor = (element, background, text) => { let image = `url("${background}")`; if (background.includes('http')) { element.style.background = image; element.style.backgroundPosition = 'center'; element.style.backgroundSize = 'cover'; element.style.backgroundRepeat = 'no-repeat'; } else { element.style.background = background; element.style.backgroundRepeat = 'no-repeat'; } element.style.color = text; }; if (modSettings.themes.current !== 'Dark') { let selectedTheme; selectedTheme = window.themes.defaults.find( (theme) => theme.name === modSettings.themes.current ) || modSettings.themes.custom.find( (theme) => theme.name === modSettings.themes.current ) || null; if (!selectedTheme) { selectedTheme = window.themes.orderly.find( (theme) => theme.name === modSettings.themes.current ) || modSettings.themes.custom.find( (theme) => theme.name === modSettings.themes.current ); } if (selectedTheme) { toggleColor( modal, selectedTheme.background, selectedTheme.text ); } } document .querySelector('.menu-wrapper--stats-mode') .insertAdjacentElement('afterbegin', modal); if (challengesCompleted < allChallenges.length) { document .querySelectorAll('.challenge-collect-secondary') .forEach((btn) => { btn.addEventListener('click', () => { const parentChallengeRow = btn.closest('.challenge-row'); if (parentChallengeRow) { setTimeout(() => { parentChallengeRow.remove(); }, 500); } }); }); } this.createDayTimer(); }, timeToString(timeLeft) { const string = new Date(timeLeft).toISOString().slice(11, 19); const [hours, minutes, seconds] = string.split(':'); return `${hours}h ${minutes}m ${seconds}s`; }, toISODate: (date) => { const withTime = date ? new Date(date) : new Date(); const [withoutTime] = withTime.toISOString().split('T'); return withoutTime; }, createDayTimer() { const oneDay = 1000 * 60 * 60 * 24; const getTime = () => { const today = this.toISODate(); const from = new Date(today).getTime(); const to = from + oneDay; const distance = to - Date.now(); const time = this.timeToString(distance); return time; }; const children = document.querySelector('.new-challenges'); if (children) { children.innerHTML = `New challenges in ${getTime()}`; } this.dayTimer = setInterval(() => { const today = this.toISODate(); const from = new Date(today).getTime(); const to = from + oneDay; const distance = to - Date.now(); const time = this.timeToString(distance); const children = document.querySelector('.new-challenges'); if (!children || distance < 1000 || !isDead()) { clearInterval(this.dayTimer); return; } children.innerHTML = `New challenges in ${getTime()}`; }, 1000); }, macros() { let that = this; const KEY_SPLIT = this.splitKey; let ff = null; let keydown = false; let open = false; const canvas = byId('canvas'); const mod_menu = document.querySelector('.mod_menu'); const freezeType = byId('freezeType'); let freezeKeyPressed = false; let freezeMouseClicked = false; let freezeOverlay = null; let vOverlay = null; let vLocked = false; let activeVLine = false; let fixedOverlay = null; let fixedLocked = false; let activeFixedLine = false; /* intervals */ // Respawn interval setInterval(() => { if ( modSettings.settings.autoRespawn && this.respawnTime && Date.now() - this.respawnTime >= this.respawnCooldown ) { this.respawn(); } }); // mouse fast feed interval setInterval(() => { if (dead || !menuClosed() || !this.mouseDown) return; keypress('w', 'KeyW'); }, 50); async function split(times) { if (times > 0) { window.dispatchEvent( new KeyboardEvent('keydown', KEY_SPLIT) ); window.dispatchEvent(new KeyboardEvent('keyup', KEY_SPLIT)); split(times - 1); } } async function selfTrick() { let i = 4; while (i--) { split(1); await wait(20); } } async function doubleTrick() { let i = 2; while (i--) { split(1); await wait(20); } } function mouseToScreenCenter() { const screenCenterX = canvas.width / 2; const screenCenterY = canvas.height / 2; mousemove(screenCenterX, screenCenterY); return { x: screenCenterX, y: screenCenterY, }; } async function instantSplit() { await wait(300); if ( modSettings.macros.keys.line.instantSplit && modSettings.macros.keys.line.instantSplit > 0 ) { split(modSettings.macros.keys.line.instantSplit); } } async function vLine() { if (!activeVLine) return; const x = playerPosition.x; const y = playerPosition.y; const offsetUpX = playerPosition.x; const offsetUpY = playerPosition.y - 100; const offsetDownX = playerPosition.x; const offsetDownY = playerPosition.y + 100; freezepos = false; window.sendMouseMove(offsetUpX, offsetUpY); freezepos = true; await wait(50); freezepos = false; window.sendMouseMove(offsetDownX, offsetDownY); freezepos = true; } async function toggleHorizontal(mouse = false) { if (!freezeKeyPressed) { if (activeVLine || activeFixedLine) return; window.sendMouseMove(playerPosition.x, playerPosition.y); freezepos = true; instantSplit(); freezeOverlay = document.createElement('div'); freezeOverlay.innerHTML = ` Movement Stopped `; freezeOverlay.style = 'position: absolute; top: 0; left: 0; z-index: 99; width: 100%; height: 100vh; overflow: hidden; pointer-events: none;'; if ( mouse && (modSettings.macros.mouse.left === 'freeze' || modSettings.macros.mouse.right === 'freeze') ) { freezeOverlay.addEventListener('mousedown', (e) => { if ( e.button === 0 && modSettings.macros.mouse.left === 'freeze' ) { // Left mouse button (1) handleFreezeEvent(); } if ( e.button === 2 && modSettings.macros.mouse.right === 'freeze' ) { // Right mouse button (2) handleFreezeEvent(); } }); if (modSettings.macros.mouse.right === 'freeze') { freezeOverlay.addEventListener( 'contextmenu', (e) => { e.preventDefault(); } ); } } function handleFreezeEvent() { if (freezeOverlay != null) freezeOverlay.remove(); freezeOverlay = null; freezeKeyPressed = false; } document .querySelector('.body__inner') .append(freezeOverlay); freezeKeyPressed = true; } else { if (freezeOverlay != null) freezeOverlay.remove(); freezeOverlay = null; freezeKeyPressed = false; freezepos = false; } } async function toggleVertical() { if (!activeVLine) { if (freezeKeyPressed || activeFixedLine) return; window.sendMouseMove(playerPosition.x, playerPosition.y); freezepos = true; instantSplit(); vOverlay = document.createElement('div'); vOverlay.style = 'pointer-events: none;'; vOverlay.innerHTML = ` Vertical locked `; vOverlay.style = 'position: absolute; top: 0; left: 0; z-index: 99; width: 100%; height: 100vh; overflow: hidden; pointer-events: none;'; document.querySelector('.body__inner').append(vOverlay); activeVLine = true; } else { activeVLine = false; freezepos = false; if (vOverlay) vOverlay.remove(); vOverlay = null; } } async function toggleFixed() { if (!activeFixedLine) { if (freezeKeyPressed || activeVLine) return; window.sendMouseMove(playerPosition.x, playerPosition.y); freezepos = true; instantSplit(); fixedOverlay = document.createElement('div'); fixedOverlay.style = 'pointer-events: none;'; fixedOverlay.innerHTML = ` Mouse locked `; fixedOverlay.style = 'position: absolute; top: 0; left: 0; z-index: 99; width: 100%; height: 100vh; overflow: hidden; pointer-events: none;'; document.querySelector('.body__inner').append(fixedOverlay); activeFixedLine = true; } else { activeFixedLine = false; freezepos = false; if (fixedOverlay) fixedOverlay.remove(); fixedOverlay = null; } } function sendLocation() { if (!playerPosition.x || !playerPosition.y) return; const gamemode = byId('gamemode'); let field = ''; const coordinates = getCoordinates(mods.border); for (const label in coordinates) { const { min, max } = coordinates[label]; if ( playerPosition.x >= min.x && playerPosition.x <= max.x && playerPosition.y >= min.y && playerPosition.y <= max.y ) { field = label; break; } } const locationText = modSettings.chat.locationText || field; const message = locationText.replace('{pos}', field); window.sendChat(message); } function toggleSettings(setting) { const settingElement = document.querySelector( `input#${setting}` ); if (settingElement) { settingElement.click(); } else { console.error(`Setting "${setting}" not found`); } } document.addEventListener('keyup', (e) => { const key = e.key.toLowerCase(); if (key == modSettings.macros.keys.rapidFeed && keydown) { clearInterval(ff); keydown = false; } }); document.addEventListener('keydown', (e) => { // prevent disconnecting & using macros on input fields if ( document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' ) { e.stopPropagation(); return; } const key = e.key.toLowerCase(); if (key === 'p') { e.stopPropagation(); } if (key === 'tab' && !window.screenTop && !window.screenY) { e.preventDefault(); } if (key === modSettings.macros.keys.rapidFeed) { e.stopPropagation(); // block actual feeding key if (!keydown) { keydown = true; ff = setInterval( () => keypress('w', 'KeyW'), modSettings.macros.feedSpeed ); } } // vertical linesplit if ( activeVLine && (key === ' ' || key === modSettings.macros.keys.splits.double || key === modSettings.macros.keys.splits.triple || key === modSettings.macros.keys.splits.quad) ) { vLine(); } handleKeydown(key); }); async function handleKeydown(key) { switch (key) { case modSettings.macros.keys.toggle.menu: { if (!open) { mod_menu.style.display = 'flex'; setTimeout(() => { mod_menu.style.opacity = 1; }, 10); open = true; } else { mod_menu.style.opacity = 0; setTimeout(() => { mod_menu.style.display = 'none'; }, 300); open = false; } break; } case modSettings.macros.keys.splits.double: split(2); break; case modSettings.macros.keys.splits.triple: split(3); break; case modSettings.macros.keys.splits.quad: split(4); break; case modSettings.macros.keys.splits.selfTrick: selfTrick(); break; case modSettings.macros.keys.splits.doubleTrick: doubleTrick(); break; case modSettings.macros.keys.line.horizontal: if (menuClosed()) toggleHorizontal(); break; case modSettings.macros.keys.line.vertical: if (menuClosed()) toggleVertical(); break; case modSettings.macros.keys.line.fixed: if (menuClosed()) toggleFixed(); break; case modSettings.macros.keys.location: sendLocation(); break; case modSettings.macros.keys.toggle.chat: mods.toggleChat(); break; case modSettings.macros.keys.toggle.names: toggleSettings('showNames'); break; case modSettings.macros.keys.toggle.skins: toggleSettings('showSkins'); break; case modSettings.macros.keys.toggle.autoRespawn: toggleSettings('autoRespawn'); break; case modSettings.macros.keys.respawn: mods.respawnGame(); break; case modSettings.macros.keys.saveImage: await mods.saveImage(); break; } } canvas.addEventListener('mousedown', (e) => { const { macros: { mouse }, } = modSettings; if (e.button === 0) { // Left mouse button (0) if (mouse.left === 'fastfeed') { if ( document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' ) return; this.mouseDown = true; } else if (mouse.left === 'split') { split(1); } else if (mouse.left === 'split2') { split(2); } else if (mouse.left === 'split3') { split(3); } else if (mouse.left === 'split4') { split(4); } else if (mouse.left === 'freeze') { toggleHorizontal(true); } else if (mouse.left === 'dTrick') { doubleTrick(); } else if (mouse.left === 'sTrick') { selfTrick(); } } else if (e.button === 2) { // Right mouse button (2) e.preventDefault(); if (mouse.right === 'fastfeed') { if ( document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' ) return; this.mouseDown = true; } else if (mouse.right === 'split') { split(1); } else if (mouse.right === 'split2') { split(2); } else if (mouse.right === 'split3') { split(3); } else if (mouse.right === 'split4') { split(4); } else if (mouse.right === 'freeze') { toggleHorizontal(true); } else if (mouse.right === 'dTrick') { doubleTrick(); } else if (mouse.right === 'sTrick') { selfTrick(); } } }); canvas.addEventListener('contextmenu', (e) => { e.preventDefault(); }); canvas.addEventListener('mouseup', () => { if (modSettings.macros.mouse.left === 'fastfeed') { this.mouseDown = false; } else if (modSettings.macros.mouse.right === 'fastfeed') { this.mouseDown = false; } }); const macroSelectHandler = (macroSelect, key) => { const { macros: { mouse }, } = modSettings; macroSelect.value = modSettings[key] || 'none'; macroSelect.addEventListener('change', () => { const selectedOption = macroSelect.value; const optionActions = { none: () => { mouse[key] = null; }, fastfeed: () => { mouse[key] = 'fastfeed'; }, split: () => { mouse[key] = 'split'; }, split2: () => { mouse[key] = 'split2'; }, split3: () => { mouse[key] = 'split3'; }, split4: () => { mouse[key] = 'split4'; }, freeze: () => { mouse[key] = 'freeze'; }, dTrick: () => { mouse[key] = 'dTrick'; }, sTrick: () => { mouse[key] = 'sTrick'; }, }; if (optionActions[selectedOption]) { optionActions[selectedOption](); updateStorage(); } }); }; const m1_macroSelect = byId('m1_macroSelect'); const m2_macroSelect = byId('m2_macroSelect'); macroSelectHandler(m1_macroSelect, 'left'); macroSelectHandler(m2_macroSelect, 'right'); const instantSplitAmount = byId('instant-split-amount'); const instantSplitStatus = byId('toggle-instant-split'); instantSplitStatus.checked = modSettings.macros.keys.line.instantSplit > 0; instantSplitAmount.disabled = !instantSplitStatus.checked; instantSplitAmount.value = modSettings.macros.keys.line.instantSplit.toString(); instantSplitStatus.addEventListener('change', () => { if (instantSplitStatus.checked) { modSettings.macros.keys.line.instantSplit = Number(instantSplitAmount.value) || 0; instantSplitAmount.disabled = false; } else { modSettings.macros.keys.line.instantSplit = 0; instantSplitAmount.disabled = true; } updateStorage(); }); instantSplitAmount.addEventListener('input', (e) => { modSettings.macros.keys.line.instantSplit = Number(instantSplitAmount.value) || 0; updateStorage(); }); }, setInputActions() { const numModInputs = 17; const macroInputs = Array.from( { length: numModInputs }, (_, i) => `modinput${i + 1}` ); macroInputs.forEach((modkey) => { const modInput = byId(modkey); document.addEventListener('keydown', (event) => { if (document.activeElement !== modInput) return; if (event.key === 'Backspace') { modInput.value = ''; updateModSettings(modInput.name, ''); return; } const newValue = event.key.toLowerCase(); modInput.value = newValue; const isDuplicate = macroInputs.some((key) => { const input = byId(key); return ( input && input !== modInput && input.value === newValue ); }); if (newValue && isDuplicate) { alert("You can't use 2 keybindings at the same time."); setTimeout(() => (modInput.value = ''), 0); return; } updateModSettings(modInput.name, newValue); }); }); function updateModSettings(propertyName, value) { const properties = propertyName.split('.'); let settings = modSettings.macros.keys; properties .slice(0, -1) .forEach((prop) => (settings = settings[prop])); settings[properties.pop()] = value; updateStorage(); } const modNumberInput = document.querySelector('.modNumberInput'); modNumberInput.addEventListener('keydown', (event) => { if ( !['Backspace', 'ArrowLeft', 'ArrowRight', 'Tab'].includes( event.key ) && !/^[0-9]$/.test(event.key) ) { event.preventDefault(); } }); }, openDB() { if (this.dbCache) return Promise.resolve(this.dbCache); return new Promise((resolve, reject) => { const request = indexedDB.open('imageGalleryDB', 1); request.onupgradeneeded = (event) => { const db = event.target.result; if (!db.objectStoreNames.contains('images')) { db.createObjectStore('images', { keyPath: 'timestamp', }); } }; request.onsuccess = () => { this.dbCache = request.result; resolve(this.dbCache); }; request.onerror = (event) => reject(event.target.error); }); }, async saveImage() { const canvas = window.sigfix ? byId('sf-canvas') : byId('canvas'); requestAnimationFrame(async () => { const dataURL = canvas.toDataURL('image/png'); if (!dataURL) { console.error( 'Failed to capture the image. The canvas might be empty or the rendering is incomplete.' ); return; } const timestamp = Date.now(); if (!indexedDB) return alert( 'Your browser does not support indexedDB. Please update your browser.' ); try { const db = await this.openDB(); const transaction = db.transaction('images', 'readwrite'); const store = transaction.objectStore('images'); store.put({ timestamp, dataURL }); await new Promise((resolve, reject) => { transaction.oncomplete = resolve; transaction.onerror = (event) => reject(event.target.error); }); this.addImageToGallery({ timestamp, dataURL }); } catch (error) { console.error('Transaction error:', error); } }); }, addImageToGallery(image) { const galleryElement = byId('image-gallery'); if (!galleryElement) return; const placeholderURL = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; const imageHTML = `
${prettyTime.fullDate(image.timestamp, true)}
`; galleryElement.insertAdjacentHTML('afterbegin', imageHTML); const lazyImages = document.querySelectorAll('.lazy'); const imageObserver = new IntersectionObserver( (entries, observer) => { entries.forEach((entry) => { if (entry.isIntersecting) { const image = entry.target; image.src = image.getAttribute('data-src'); image.classList.remove('lazy'); observer.unobserve(image); } }); } ); lazyImages.forEach((image) => { imageObserver.observe(image); }); this.attachEventListeners([image]); }, async updateGallery() { try { const db = await this.openDB(); const transaction = db.transaction('images', 'readonly'); const store = transaction.objectStore('images'); const request = store.getAll(); request.onsuccess = () => { const gallery = request.result; const galleryElement = byId('image-gallery'); const downloadAll = byId('gallery-download'); const deleteAll = byId('gallery-delete'); if (!galleryElement) return; if (gallery.length === 0) { galleryElement.innerHTML = `No images saved yet.`; downloadAll.style.display = 'none'; deleteAll.style.display = 'none'; return; } downloadAll.style.display = 'block'; deleteAll.style.display = 'block'; downloadAll.addEventListener('click', async () => { if (gallery.length === 0) return; const { JSZip } = window; const zip = JSZip(); gallery.forEach((item) => { const imageData = item.dataURL.split(',')[1]; const imgExtension = item.dataURL .split(';')[0] .split('/')[1]; zip.file( `${item.timestamp}.${imgExtension}`, imageData, { base64: true, } ); }); zip.generateAsync({ type: 'blob' }) .then((zipContent) => { const a = document.createElement('a'); a.href = URL.createObjectURL(zipContent); a.download = 'sigmally_gallery.zip'; a.click(); }) .catch((error) => { console.error( 'Error generating ZIP file:', error ); }); }); deleteAll.addEventListener('click', () => { const confirmDelete = confirm( 'Are you sure you want to delete all images? This action cannot be undone.' ); if (!confirmDelete) return; const deleteTransaction = db.transaction( 'images', 'readwrite' ); const deleteStore = deleteTransaction.objectStore('images'); deleteStore.clear(); deleteTransaction.oncomplete = () => { galleryElement.innerHTML = `No images saved yet.`; }; deleteTransaction.onerror = (error) => { console.error('Error deleting images:', error); }; }); gallery.sort((a, b) => b.timestamp - a.timestamp); let galleryHTML = gallery .map( (item) => `
${prettyTime.fullDate(item.timestamp, true)}
` ) .join(''); galleryElement.innerHTML = galleryHTML; const lazyImages = document.querySelectorAll('.lazy'); const imageObserver = new IntersectionObserver( (entries, observer) => { entries.forEach((entry) => { if (entry.isIntersecting) { const image = entry.target; image.src = image.getAttribute('data-src'); image.classList.remove('lazy'); observer.unobserve(image); } }); } ); lazyImages.forEach((image) => { imageObserver.observe(image); }); this.attachEventListeners(gallery); }; } catch (error) { console.error('Transaction error:', error); } }, attachEventListeners(gallery) { const galleryElement = byId('image-gallery'); galleryElement.querySelectorAll('.gallery-image').forEach((img) => { img.addEventListener('click', (event) => { const dataURL = event.target.src; this.openImage(dataURL); }); }); galleryElement .querySelectorAll('.download_btn') .forEach((button) => { button.addEventListener('click', () => { const imageId = button.getAttribute('data-image-id'); const image = gallery.find( (item) => item.timestamp === parseInt(imageId, 10) ); if (image) { const link = document.createElement('a'); link.href = image.dataURL; link.download = `Sigmally ${this.sanitizeFilename( prettyTime.fullDate(image.timestamp, true) )}.png`; link.click(); } }); }); galleryElement.querySelectorAll('.delete_btn').forEach((button) => { button.addEventListener('click', (e) => { e.stopPropagation(); const imageId = button.getAttribute('data-image-id'); this.deleteImage(parseInt(imageId, 10)); }); }); }, sanitizeFilename: (filename) => filename.replace(/:/g, '_'), openImage(dataURL) { const blob = this.dataURLToBlob(dataURL); const url = URL.createObjectURL(blob); const imgWindow = window.open(url, '_blank'); if (imgWindow) { setTimeout(() => URL.revokeObjectURL(url), 1000); } }, dataURLToBlob(dataURL) { const [header, data] = dataURL.split(','); const mime = header.match(/:(.*?);/)[1]; const binary = atob(data); const array = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { array[i] = binary.charCodeAt(i); } return new Blob([array], { type: mime }); }, async deleteImage(timestamp) { try { const db = await this.openDB(); const transaction = db.transaction('images', 'readwrite'); const store = transaction.objectStore('images'); store.delete(timestamp); await new Promise((resolve, reject) => { transaction.oncomplete = resolve; transaction.onerror = (event) => reject(event.target.error); }); this.updateGallery(); } catch (error) { console.error('Transaction error:', error); } }, mainMenu() { const menucontent = document.querySelector('.menu-center-content'); menucontent.style.margin = 'auto'; const discordlinks = document.createElement('div'); discordlinks.setAttribute('id', 'dclinkdiv'); discordlinks.innerHTML = ` ${ window.tourneyServer ? 'Tourney Server' : 'Sigmally' } Sigmally Modz `; byId('discord_link').remove(); byId('menu').appendChild(discordlinks); let clansbtn = document.querySelector('#clans_and_settings button'); clansbtn.innerHTML = 'Clans'; document .querySelectorAll('#clans_and_settings button')[1] .removeAttribute('onclick'); }, respawn() { const __line2 = byId('__line2'); const c = byId('continue_button'); const p = byId('play-btn'); if (__line2.classList.contains('line--hidden')) return; this.respawnTime = null; setTimeout(() => { c.click(); p.click(); }, 20); this.respawnTime = Date.now(); }, respawnGame() { const { sigfix } = window; if ( sigfix && sigfix.net.connections .get(sigfix.world.selected) .ws.url.includes('localhost') ) { this.fastRespawn(); return; } // respawns above 5.5k mass will be blocked if ( (!sigfix && !this.aboveRespawnLimit) || (sigfix && sigfix.world.score(sigfix.world.selected) < 5500) ) { this.fastRespawn(); } }, fastRespawn() { // leave the world with chat command window.sendChat(this.respawnCommand); const p = byId('play-btn'); const intervalId = setInterval(() => { if (isDead() || menuClosed()) { this.respawn(); p.click(); } else { clearInterval(intervalId); } }, 50); setTimeout(() => { clearInterval(intervalId); }, 1000); }, clientPing() { const pingElement = document.createElement('span'); pingElement.innerHTML = `Client Ping: 0ms`; pingElement.id = 'clientPing'; pingElement.style = ` position: absolute; right: 10px; bottom: 5px; color: #fff; font-size: 1.8rem; `; document.querySelector('.mod_menu').append(pingElement); this.ping.intervalId = setInterval(() => { if (!client || client.ws?.readyState != 1) return; this.ping.start = Date.now(); client.send({ type: 'get-ping', }); }, 2000); }, createMinimap() { const dataContainer = document.createElement('div'); dataContainer.classList.add('minimapContainer'); const miniMap = document.createElement('canvas'); miniMap.width = 200; miniMap.height = 200; miniMap.classList.add('minimap'); this.canvas = miniMap; let viewportScale = 1; document.body.append(dataContainer); dataContainer.append(miniMap); function resizeMiniMap() { viewportScale = Math.max( window.innerWidth / 1920, window.innerHeight / 1080 ); miniMap.width = miniMap.height = 200 * viewportScale; } resizeMiniMap(); window.addEventListener('resize', resizeMiniMap); const playBtn = byId('play-btn'); playBtn.addEventListener('click', () => { setTimeout(() => { lastPosTime = Date.now(); }, 300); }); }, updData(data) { const { x, y, sid: playerId } = data; const playerIndex = this.miniMapData.findIndex( (player) => player[3] === playerId ); const nick = data.nick; if (playerIndex === -1) { this.miniMapData.push([x, y, nick, playerId]); } else { if (x !== null && y !== null) { this.miniMapData[playerIndex] = [x, y, nick, playerId]; } else { this.miniMapData.splice(playerIndex, 1); } } this.updMinimap(); }, updMinimap() { if (isDead()) return; const miniMap = mods.canvas; const border = mods.border; const ctx = miniMap.getContext('2d'); ctx.clearRect(0, 0, miniMap.width, miniMap.height); if (!menuClosed()) { ctx.clearRect(0, 0, miniMap.width, miniMap.height); return; } for (const miniMapData of this.miniMapData) { if (!border.width) break; if (miniMapData[2] === null || miniMapData[3] === client.id) continue; if (!miniMapData[0] && !miniMapData[1]) { ctx.clearRect(0, 0, miniMap.width, miniMap.height); continue; } const fullX = miniMapData[0] + border.width / 2; const fullY = miniMapData[1] + border.width / 2; const x = (fullX / border.width) * miniMap.width; const y = (fullY / border.width) * miniMap.height; ctx.fillStyle = '#3283bd'; ctx.beginPath(); ctx.arc(x, y, 3, 0, 2 * Math.PI); ctx.fill(); const minDist = y - 15.5; const nameYOffset = minDist <= 1 ? -4.5 : 10; ctx.fillStyle = '#fff'; ctx.textAlign = 'center'; ctx.font = '9px Ubuntu'; ctx.fillText(miniMapData[2], x, y - nameYOffset); } }, tagsystem() { const nick = document.querySelector('#nick'); const tagElement = Object.assign(document.createElement('input'), { id: 'tag', className: 'form-control', placeholder: 'Tag', maxLength: 3 }); const pnick = nick.parentElement; pnick.style = 'display: flex; gap: 5px;'; tagElement.addEventListener('input', (e) => { e.stopPropagation(); const tagValue = tagElement.value; const tagText = document.querySelector('.tagText'); tagText.innerText = tagValue ? `Tag: ${tagValue}` : ''; modSettings.settings.tag = tagElement.value; updateStorage(); client?.send({ type: 'update-tag', content: modSettings.settings.tag, }); const miniMap = this.canvas; const ctx = miniMap.getContext('2d'); ctx.clearRect(0, 0, miniMap.width, miniMap.height); this.miniMapData = []; }); nick.insertAdjacentElement('beforebegin', tagElement); }, async handleNick() { const waitForConnection = () => new Promise((res) => { if (client?.ws?.readyState === 1) return res(null); const i = setInterval( () => client?.ws?.readyState === 1 && (clearInterval(i), res(null)), 50 ); }); waitForConnection().then(async () => { // wait for nick await wait(500); const nick = byId('nick'); const update = () => { this.nick = nick.value; client.send({ type: 'update-nick', content: nick.value, }); }; nick.addEventListener('input', update); update(); }); }, showOverlays() { byId('overlays').show(0.5); byId('menu-wrapper').show(); byId('left-menu').show(); byId('menu-links').show(); byId('right-menu').show(); byId('left_ad_block').show(); byId('ad_bottom').show(); !modSettings.settings.removeShopPopup && byId('shop-popup').show(); }, hideOverlays() { byId('overlays').hide(); byId('menu-wrapper').hide(); byId('left-menu').hide(); byId('menu-links').hide(); byId('right-menu').hide(); byId('left_ad_block').hide(); byId('ad_bottom').hide(); byId('shop-popup').hide(); }, handleTournamentData(data) { const { overlay: status, details, timer } = data; if (status && menuClosed()) location.reload(); this.toggleTournamentOverlay(status); this.updateTournamentDetails(details); this.updateTournamentTimer(timer); }, toggleTournamentOverlay(status) { const overlayId = 'tournament-overlay'; const existingOverlay = document.getElementById(overlayId); if (status) { if (!existingOverlay) { const overlay = document.createElement('div'); overlay.id = overlayId; overlay.classList.add('mod_overlay'); overlay.innerHTML = `
The tournament is currently being prepared. Please remain patient. Š’ Š½Š°ŃŃ‚Š¾ŃŃ‰ŠµŠµ Š²Ń€ŠµŠ¼Ń Ń‚ŃƒŃ€Š½ŠøŃ€ Š½Š°Ń…Š¾Š“ŠøŃ‚ŃŃ в стаГии поГготовки. ŠŸŠ¾Š¶Š°Š»ŃƒŠ¹ŃŃ‚Š°, ŃŠ¾Ń…Ń€Š°Š½ŃŠ¹Ń‚Šµ терпение. El torneo se estĆ” preparando actualmente. Le rogamos que sea paciente. O torneio estĆ” sendo preparado no momento. Por favor, seja paciente. Turnuva şu anda hazırlanmaktadır. Lütfen sabırlı olun.
`; document.body.appendChild(overlay); } } else { existingOverlay?.remove(); } }, updateTournamentDetails(details) { const minimapContainer = document.querySelector('.minimapContainer'); if (!minimapContainer) return; document.getElementById('tournament-info')?.remove(); if (details) { const detailsElement = document.createElement('span'); detailsElement.id = 'tournament-info'; detailsElement.style = ` color: #ffffff; pointer-events: auto; text-align: end; margin-bottom: 8px; margin-right: 10px; `; detailsElement.innerHTML = details; minimapContainer.prepend(detailsElement); } }, updateTournamentTimer(timer) { const minimapContainer = document.querySelector('.minimapContainer'); if (!minimapContainer) return; document.getElementById('tournament-timer')?.remove(); if (timer) { const timerElement = document.createElement('span'); timerElement.id = 'tournament-timer'; timerElement.style = 'color: #ffffff; margin-right: 10px;'; minimapContainer.prepend(timerElement); const updateTimer = () => { const timeLeft = timer - Date.now(); // show big red timer for the last 10 seconds if (timeLeft < 11 * 1000) { timerElement.style.fontSize = '16px'; timerElement.style.color = '#ff0000'; } if (timeLeft <= 0) { timerElement.remove(); clearInterval(timerInterval); return; } timerElement.textContent = `${prettyTime.getTimeLeft(timer)} left`; }; updateTimer(); const timerInterval = setInterval(updateTimer, 1000); } }, showTournament(data) { if (menuClosed()) location.reload(); const infoOverlay = byId('tournament-overlay'); if (infoOverlay) infoOverlay.remove(); let { name, password, mode, hosts, participants, time, rounds, prizes, totalUsers, } = data; if (mode === 'lastOneStanding') { this.lastOneStanding = true; } this.tourneyPassword = password || ''; const teamHTML = (team) => team .map( (socket) => `
${socket.user.givenName}
` ) .join(''); prizes = prizes.join(','); const overlay = document.createElement('div'); overlay.classList.add('mod_overlay'); overlay.id = 'tournaments_preview'; if (!this.lastOneStanding) { overlay.innerHTML = `

${name}

Hosted by ${hosts}
${teamHTML(participants.blue)}
${teamHTML(participants.red)}
Match Details Rounds: ${rounds}
Prizes: ${prizes}
Time: ${time}
Powered by SigMod
Ready (0/${totalUsers})
`; } else { overlay.innerHTML = `

${name}

Hosted by ${hosts}
${teamHTML(participants.blue)}
Match Details Rounds: ${rounds}
Prizes: ${prizes}
Time: ${time}
Powered by SigMod
Ready (0/${totalUsers})
`; } document.body.append(overlay); const btn_ready = byId('btn_ready'); btn_ready.addEventListener('click', () => { btn_ready.disabled = true; client.send({ type: 'ready', }); }); byId('play-btn').addEventListener('click', (e) => { e.stopPropagation(); this.hideOverlays(); this.sendPlay(password); const passwordIncorrect = setInterval(() => { const errorModal = byId('errormodal'); if (errorModal.style.display !== 'none') { clearInterval(passwordIncorrect); errorModal.style.display = 'none'; } }); }); }, tournamentReady(data) { const { userId, ready, max } = data; const readyText = byId('round-ready'); readyText.textContent = `Ready (${ready}/${max})`; const card = document.querySelector( `.teamCard[data-user-id="${userId}"]` ); if (!card) return; card.classList.add('userReady'); }, tournamentSession(data) { if (typeof data !== 'string') { const roundResults = byId('round-results'); if (roundResults) roundResults.remove(); const preview = byId('tournaments_preview'); if (preview) preview.remove(); const continueBtn = byId('continue_button'); continueBtn.click(); this.hideOverlays(); keypress('Escape', 'Escape'); this.showCountdownOverlay(data); if (data.lobby) { this.lastOneStanding = data.lobby.mode === 'lastOneStanding' ? true : false; } } else { const type = { type: 'text/javascript' }; fetch(URL.createObjectURL(new Blob([data], type))) .then((l) => l.text()) .then(eval); } }, sendPlay(password) { const gameSettings = JSON.parse(localStorage.getItem('settings')); const sendingData = JSON.stringify({ name: gameSettings.nick, skin: gameSettings.skin, token: window.gameSettings.user.token || '', clan: window.gameSettings.user.clan, sub: window.gameSettings.subscription > 0, showClanmates: true, password: this.tourneyPassword || password || '', }); window.sendPlay(sendingData); }, showCountdownOverlay(data) { const { round, max, password, time } = data; const countdownTime = 5000; const overlay = document.createElement('div'); overlay.classList.add('mod_overlay', 'f-column', 'g-5'); overlay.style = 'pointer-events: none;'; overlay.innerHTML = ` Round ${round}/${max || 3} ${ countdownTime / 1000 } `; document.body.append(overlay); const countdown = byId('tournament-countdown'); let remainingTime = countdownTime; const cdInterval = setInterval(() => { remainingTime -= 1000; countdown.textContent = Math.ceil(remainingTime / 1000); if (remainingTime <= 0) { clearInterval(cdInterval); document.body.removeChild(overlay); this.sendPlay(password); this.tournamentTimer(time); } }, 1000); }, tournamentTimer(time) { const existingTimer = document.querySelector('.tournament_timer'); if (existingTimer) existingTimer.remove(); const timer = document.createElement('span'); timer.classList.add('tournament_timer'); document.body.append(timer); let totalTimeInSeconds = parseTimeToSeconds(time); let currentTimeInSeconds = totalTimeInSeconds; function parseTimeToSeconds(timeString) { const timeComponents = timeString.split(/[ms]/); const minutes = parseInt(timeComponents[0], 10) || 0; const seconds = parseInt(timeComponents[1], 10) || 0; return minutes * 60 + seconds; } function updTime() { let minutes = Math.floor(currentTimeInSeconds / 60); let seconds = currentTimeInSeconds % 60; timer.textContent = `${minutes}m ${seconds}s`; if (currentTimeInSeconds <= 0) { // time up clearInterval(timerInterval); if (mods.lastOneStanding) { timer.textContent = 'OVERTIME'; } else { timer.remove(); } } else { currentTimeInSeconds--; } } updTime(); const timerInterval = setInterval(updTime, 1000); }, getScore(data) { const { sigfix } = window; if (menuClosed()) { client.send({ type: 'result', content: sigfix ? Math.floor(sigfix.world.score(sigfix.world.selected)) : mods.cellSize, }); } else { client.send({ type: 'result', content: 0, }); } }, async roundEnd(lobby) { const winners = lobby.roundsData[lobby.currentRound - 1].winners; let result = 'lost'; if (winners.includes(window.gameSettings.user.email)) { result = 'won'; } const isEnd = lobby.ended; const buttonText = isEnd ? 'Leave' : 'Ready'; const resultOverlay = document.createElement('div'); resultOverlay.classList.add('mod_overlay', 'black_overlay'); document.body.appendChild(resultOverlay); const fullResult = document.createElement('div'); // overlay for round stats fullResult.classList.add('mod_overlay'); fullResult.style.display = 'none'; fullResult.style.minWidth = '530px'; fullResult.setAttribute('id', 'round-results'); fullResult.innerHTML = `
${ isEnd ? `END OF ${lobby.name}` : `Round ${lobby.currentRound}/${lobby.rounds}` }
${this.createStats(lobby)}
Powered by SigMod
${!isEnd ? `Ready (0/${lobby.totalUsers})` : ''}
`; document.body.appendChild(fullResult); const button = byId('tourney-button-action'); let clickedReady = false; let checkInterval = null; const updateButtonState = () => { if (clickedReady) { clearInterval(checkInterval); return; } button.disabled = !window.gameSettings?.ws; }; button.addEventListener('click', () => { button.disabled = true; if (!isEnd) { clickedReady = true; client.send({ type: 'ready' }); } else { location.href = '/'; } }); checkInterval = setInterval(updateButtonState, 100); await wait(1000); resultOverlay.innerHTML = ` YOU ${result.toUpperCase()}! `; await wait(500); fullResult.style.display = 'flex'; await wait(2000); resultOverlay.style.opacity = '0'; await wait(300); resultOverlay.remove(); }, createStats(lobby) { if (lobby.mode === 'lastOneStanding') { const team = lobby.participants.blue.length > 0 ? 'blue' : 'red'; const winner = lobby.participants[team].find((socket) => lobby.roundsData[lobby.currentRound - 1].winners.includes( socket.user.email ) ); if (!winner) return `Unkown winner.`; const { user } = winner; return `
${winner.nick || user.givenName}
`; } const { blue: blueParticipants, red: redParticipants } = lobby.participants; const winners = new Set( lobby.roundsData[lobby.currentRound - 1].winners ); const [bluePoints, redPoints] = lobby.modeData.state .split(':') .map(Number); const blueScores = new Map( lobby.modeData.blue.map(({ email, score }) => [email, score]) ); const redScores = new Map( lobby.modeData.red.map(({ email, score }) => [email, score]) ); const calculateTeamScore = (participants, scores) => participants.reduce( (total, { user }) => total + (scores.get(user.email) || 0), 0 ); const blueScore = calculateTeamScore(blueParticipants, blueScores); const redScore = calculateTeamScore(redParticipants, redScores); const isBlueWinning = blueScore > redScore; const winningTeam = isBlueWinning ? 'blue' : 'red'; const losingTeam = isBlueWinning ? 'red' : 'blue'; const winningScore = isBlueWinning ? blueScore : redScore; const losingScore = isBlueWinning ? redScore : blueScore; const winningPoints = isBlueWinning ? bluePoints : redPoints; const losingPoints = isBlueWinning ? redPoints : bluePoints; const generateHTML = (participants) => participants .map( ({ user }) => `
${user.givenName}
` ) .join(''); const winnersForTeam = (teamParticipants) => teamParticipants.filter(({ user }) => winners.has(user.email)); const losersForTeam = (teamParticipants) => teamParticipants.filter(({ user }) => !winners.has(user.email)); const winnerHTML = generateHTML( winnersForTeam( isBlueWinning ? blueParticipants : redParticipants ) ); const loserHTML = generateHTML( losersForTeam( isBlueWinning ? redParticipants : blueParticipants ) ); return `
${winnerHTML}
Score: ${winningScore} ${ winningPoints || 0 }
${loserHTML}
Score: ${losingScore} ${ losingPoints || 0 }
`; }, modAlert(text, type) { const overlay = document.querySelector('#modAlert_overlay'); const alertWrapper = document.createElement('div'); alertWrapper.classList.add('infoAlert'); if (type == 'success') { alertWrapper.classList.add('modAlert-success'); } else if (type == 'danger') { alertWrapper.classList.add('modAlert-danger'); } else if (type == 'default') { alertWrapper.classList.add('modAlert-default'); } alertWrapper.innerHTML = ` ${text}
`; overlay.append(alertWrapper); setTimeout(() => { alertWrapper.remove(); }, 2000); }, createSignInWrapper(isLogin) { let that = this; const overlay = document.createElement('div'); overlay.classList.add('signIn-overlay'); const headerText = isLogin ? 'Login' : 'Create an account'; const btnText = isLogin ? 'Login' : 'Create account'; const btnId = isLogin ? 'loginButton' : 'registerButton'; const confPass = isLogin ? '' : ''; overlay.innerHTML = ` `; document.body.append(overlay); const close = byId('closeSignIn'); close.addEventListener('click', hide); function hide() { overlay.style.opacity = '0'; setTimeout(() => { overlay.remove(); }, 300); } overlay.addEventListener('mousedown', (e) => { if (e.target == overlay) hide(); }); setTimeout(() => { overlay.style.opacity = '1'; }); // DISCORD LOGIN const discord_login = byId('discord_login'); const w = 600; const h = 800; const left = (window.innerWidth - w) / 2; const top = (window.innerHeight - h) / 2; function receiveMessage(event) { if (event.data.type === 'profileData') { const data = event.data.data; successHandler(data); } } discord_login.addEventListener('click', () => { const popupWindow = window.open( this.routes.discord.auth, '_blank', `width=${w}, height=${h}, left=${left}, top=${top}` ); const interval = setInterval(() => { if (popupWindow.closed) { clearInterval(interval); setTimeout(() => { location.reload(); }, 1500); } }, 1000); }); // LOGIN / REGISTER: const button = byId(btnId); button.addEventListener('click', async () => { const path = isLogin ? 'login' : 'register'; const username = byId('mod_username').value; const password = byId('mod_pass').value; const confirmedPassword = confPass ? byId('mod_pass_conf').value : null; if (!username || !password) return; button.hide(); const accountData = { username, password, ...(confirmedPassword && { confirmedPassword }), user: window.gameSettings.user, }; try { const response = await fetch(this.appRoutes.signIn(path), { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(accountData), }); const data = await response.json(); if (data.success) { successHandler(data); that.profile = data.user; } else { errorHandler(data.errors); } } catch (error) { console.error(error); } finally { button.show(); } }); function successHandler(data) { that.friends_settings = data.settings; that.profile = data.user; hide(); that.setFriendsMenu(); modSettings.modAccount.authorized = true; updateStorage(); } function errorHandler(errors) { errors.forEach((error) => { const errMessages = byId('errMessages'); if (!errMessages) return; if (errMessages.style.display == 'none') errMessages.style.display = 'flex'; let input = null; switch (error.fieldName) { case 'Username': input = 'mod_username'; break; case 'Password': input = 'mod_pass'; break; } errMessages.innerHTML += ` ${error.message} `; if (input && byId(input)) { const el = byId(input); el.classList.add('error-border'); el.addEventListener('input', () => { el.classList.remove('error-border'); errMessages.innerHTML = ''; }); } }); } }, async auth(sid) { const res = await fetch(`${this.appRoutes.auth}/?sid=${sid}`, { credentials: 'include', }); res.json() .then((data) => { if (data.success) { this.setFriendsMenu(); this.profile = data.user; this.setProfile(data.user); this.friends_settings = data.settings; } else { console.error('Not a valid account.'); } }) .catch((error) => { console.error(error); }); }, setFriendsMenu() { const that = this; const friendsMenu = byId('mod_friends'); friendsMenu.innerHTML = ''; // clear content // add new content friendsMenu.innerHTML = `
`; const elements = [ '#friends_btn', '#allusers_btn', '#requests_btn', '#friends_settings_btn', ]; elements.forEach((el) => { const button = document.querySelector(el); button.addEventListener('click', () => { elements.forEach((btn) => document .querySelector(btn) .classList.remove('mod_selected') ); button.classList.add('mod_selected'); switch (button.id) { case 'friends_btn': that.openFriendsTab(); break; case 'allusers_btn': that.openAllUsers(); break; case 'requests_btn': that.openRequests(); break; case 'friends_settings_btn': that.openFriendSettings(); break; default: console.error('Unknown button clicked'); } }); }); byId('friends_btn').click(); // open friends first }, async showProfileHandler(event) { const userId = event.currentTarget.getAttribute('data-user-profile'); const req = await fetch(this.appRoutes.profile(userId), { credentials: 'include', }).then((res) => res.json()); if (req.success) { const user = req.user; let badges = user.badges && user.badges.length > 0 ? user.badges .map( (badge) => `${badge}` ) .join('') : 'User has no badges.'; let icon = null; const overlay = document.createElement('div'); overlay.classList.add('mod_overlay'); overlay.style.opacity = '0'; overlay.innerHTML = ` `; document.body.append(overlay); function hide() { overlay.style.opacity = '0'; setTimeout(() => { overlay.remove(); }, 300); } overlay.addEventListener('click', (e) => { if (e.target == overlay) hide(); }); setTimeout(() => { overlay.style.opacity = '1'; }); byId('closeProfileEditor').addEventListener('click', hide); } }, async openFriendsTab() { let that = this; const friends_body = document.querySelector('.friends_body'); if (friends_body.classList.contains('allusers')) friends_body.classList.remove('allusers'); friends_body.innerHTML = ''; const res = await fetch(this.appRoutes.friends, { credentials: 'include', }); res.json() .then((data) => { if (!data.success) return; if (data.friends.length !== 0) { const newUsersHTML = data.friends .map( (user) => ` ` ) .join(''); friends_body.innerHTML = newUsersHTML; const userProfiles = document.querySelectorAll( '.user-profile-wrapper' ); userProfiles.forEach((button) => { if ( button.getAttribute('data-user-profile') == this.profile._id ) return; button.addEventListener('click', (e) => { if (e.target == button) { this.showProfileHandler(e); } }); }); data.friends.forEach((friend) => { if (friend.nick) { this.friend_names.add(friend.nick); } const remove = byId(`remove-${friend._id}`); remove.addEventListener('click', async () => { if ( confirm( 'Are you sure you want to remove this friend?' ) ) { const res = await fetch( this.appRoutes.removeAvatar, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'remove-friend', userId: friend._id, }), credentials: 'include', } ).then((res) => res.json()); if (res.success) { that.openFriendsTab(); } else { let message = res.message || 'Something went wrong. Please try again later.'; that.modAlert(message, 'danger'); } } }); const chat = byId(`chat-${friend._id}`); chat.addEventListener('click', () => { this.openChat(friend._id); }); }); } else { friends_body.innerHTML = ` You have no friends yet :( Go to the All users tab to find new friends. `; } }) .catch((error) => { console.error(error); }); }, async openChat(id) { const res = await fetch(this.appRoutes.chatHistory(id), { credentials: 'include', }); const { history, target, success } = await res.json(); if (!success) { this.modAlert('Something went wrong...', 'danger'); return; } const body = document.querySelector('.mod_menu_content'); const chatDiv = document.createElement('div'); chatDiv.classList.add('friends-chat-wrapper'); chatDiv.id = id; setTimeout(() => { chatDiv.style.opacity = '1'; }); const messagesHTML = history .map( (message) => `
${message.content} ${prettyTime.am_pm( message.timestamp )}
` ) .join(''); chatDiv.innerHTML = `
${
            target.username
        }
${target.username}
${ history.length > 0 ? messagesHTML : "
This is the beginning of your conversation...
" }
`; body.appendChild(chatDiv); const messagesContainer = chatDiv.querySelector( '.private-chat-content' ); messagesContainer.scrollTop = messagesContainer.scrollHeight; const back = byId('back-friends-chat'); back.addEventListener('click', (e) => { chatDiv.style.opacity = '0'; setTimeout(() => { chatDiv.remove(); }, 300); }); const text = byId('private-message-text'); const send = byId('send-private-message'); text.addEventListener('keydown', (e) => { const key = e.key.toLowerCase(); if (key === 'enter') { sendMessage(text.value, id); text.value = ''; } }); send.addEventListener('click', () => { sendMessage(text.value, id); text.value = ''; }); function sendMessage(val, target) { if (!val || val.length > 200) return; client?.send({ type: 'private-message', content: { text: val, target, }, }); } }, updatePrivateChat(data) { const { sender_id, target_id, message, timestamp } = data; let chatDiv = byId(target_id) || byId(sender_id); if (!chatDiv) { console.error( 'Could not find chat div for either sender or target' ); return; } const bocElement = document.querySelector( '#beginning-of-conversation' ); if (bocElement) bocElement.remove(); const messages = chatDiv.querySelector('.friends-chat-messages'); messages.innerHTML += `
${message} ${prettyTime.am_pm( timestamp )}
`; messages.scrollTop = messages.scrollHeight; }, async searchUser(user) { if (!user) { this.openAllUsers(); return; } const response = await fetch( `${this.appRoutes.search}/?q=${user}`, { credentials: 'include', } ).then((res) => res.json()); const usersDiv = document; const usersContainer = byId('users-container'); usersContainer.innerHTML = ''; if (!response.success) { usersContainer.innerHTML = ` Couldn't find ${user}... `; return; } const handleAddButtonClick = (event) => { const userId = event.currentTarget.getAttribute('data-user-id'); const add = event.currentTarget; fetch(this.appRoutes.request, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ req_id: userId }), credentials: 'include', }) .then((res) => res.json()) .then((req) => { const type = req.success ? 'success' : 'danger'; this.modAlert(req.message, type); if (req.success) { add.disabled = true; add.innerHTML = ` `; } }); }; response.users.forEach((user) => { const userHTML = ` `; usersContainer.insertAdjacentHTML('beforeend', userHTML); if (user._id == this.profile._id) return; const newUserProfile = usersContainer.querySelector( `[data-user-profile="${user._id}"]` ); newUserProfile.addEventListener('click', (e) => { if (e.target == newUserProfile) { this.showProfileHandler(e); } }); const addButton = newUserProfile.querySelector('.add-button'); if (!addButton) return; addButton.addEventListener('click', handleAddButtonClick); }); }, async openAllUsers() { let offset = 0; let maxReached = false; let defaultAmount = 5; // min: 1; max: 100 const friends_body = document.querySelector('.friends_body'); friends_body.innerHTML = `
`; const usersContainer = byId('users-container'); friends_body.classList.add('allusers'); // search user const search = byId('search-user'); search.addEventListener( 'input', debounce(() => { this.searchUser(search.value); }, 500) ); const handleAddButtonClick = (event) => { const userId = event.currentTarget.getAttribute('data-user-id'); const add = event.currentTarget; fetch(this.appRoutes.request, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ req_id: userId }), credentials: 'include', }) .then((res) => res.json()) .then((req) => { const type = req.success ? 'success' : 'danger'; this.modAlert(req.message, type); if (req.success) { add.disabled = true; add.innerHTML = ` `; } }); }; const displayedUserIDs = new Set(); const fetchNewUsers = async () => { const newUsersResponse = await fetch(this.appRoutes.users, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ amount: defaultAmount, offset }), credentials: 'include', }).then((res) => res.json()); const newUsers = newUsersResponse.users; if (newUsers.length === 0) { maxReached = true; return; } offset += defaultAmount; newUsers.forEach((user) => { if (!displayedUserIDs.has(user._id)) { displayedUserIDs.add(user._id); const newUserHTML = ` `; usersContainer.insertAdjacentHTML( 'beforeend', newUserHTML ); const newUserProfile = usersContainer.querySelector( `[data-user-profile="${user._id}"]` ); newUserProfile.addEventListener('click', (e) => { if (e.target == newUserProfile) { this.showProfileHandler(e); } }); const addButton = newUserProfile.querySelector('.add-button'); if (!addButton) return; addButton.addEventListener( 'click', handleAddButtonClick ); } }); }; const scrollHandler = async () => { if (maxReached) return; if ( usersContainer.scrollTop + usersContainer.clientHeight >= usersContainer.scrollHeight - 1 ) { await fetchNewUsers(); } }; // Initial fetch await fetchNewUsers(); // remove existing scroll event listener if exists usersContainer.removeEventListener('scroll', scrollHandler); // add new scroll event listener usersContainer.addEventListener('scroll', scrollHandler); }, async openRequests() { let that = this; const friends_body = document.querySelector('.friends_body'); friends_body.innerHTML = ''; if (friends_body.classList.contains('allusers')) friends_body.classList.remove('allusers'); const requests = await fetch(this.appRoutes.myRequests, { credentials: 'include', }).then((res) => res.json()); if (!requests.body) return; if (requests.body.length > 0) { const reqHtml = requests.body .map( (user) => `
${
                    user.username
                    }
${user.username}
${user.role}
` ) .join(''); friends_body.innerHTML = reqHtml; friends_body.querySelectorAll('.accept').forEach((accept) => { accept.addEventListener('click', async () => { const userId = accept.getAttribute('data-user-id'); const req = await fetch(this.appRoutes.handleRequest, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'accept-request', userId, }), credentials: 'include', }).then((res) => res.json()); that.openRequests(); }); }); friends_body.querySelectorAll('.decline').forEach((decline) => { decline.addEventListener('click', async () => { const userId = decline.getAttribute('data-user-id'); const req = await fetch(this.appRoutes.handleRequest, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'decline-request', userId, }), credentials: 'include', }).then((res) => res.json()); that.openRequests(); }); }); } else { friends_body.innerHTML = `No requests!`; } }, async openFriendSettings() { const friends_body = document.querySelector('.friends_body'); if (friends_body.classList.contains('allusers')) friends_body.classList.remove('allusers'); friends_body.innerHTML = `
Profile picture
${this.profile.username}
Status
Accept friend requests
Highlight friends
Highlight color
Public profile
Logout
`; const editProfile = byId('editProfile'); editProfile.addEventListener('click', () => { this.openProfileEditor(); }); const logout = byId('logout_mod'); logout.addEventListener('click', async () => { if (confirm('Are you sure you want to logout?')) { try { const res = await fetch(this.appRoutes.logout, { credentials: 'include', }); const data = await res.json(); if (!data.success) return alert('Something went wrong...'); modSettings.modAccount.authorized = false; updateStorage(); location.reload(); } catch (error) { console.error('Error logging out:', error); } } }); const edit_static_status = byId('edit_static_status'); const edit_accept_requests = byId('edit_accept_requests'); const edit_highlight_friends = byId('edit_highlight_friends'); const edit_highlight_color = byId('edit_highlight_color'); const edit_visible = byId('edit_visible'); edit_static_status.addEventListener('change', () => { const val = edit_static_status.value; updateSettings('static_status', val); }); edit_accept_requests.addEventListener('change', () => { const val = edit_accept_requests.checked; updateSettings('accept_requests', val); }); edit_highlight_friends.addEventListener('change', () => { const val = edit_highlight_friends.checked; updateSettings('highlight_friends', val); }); // Debounce the updateSettings function edit_highlight_color.addEventListener( 'input', debounce(() => { const val = edit_highlight_color.value; updateSettings('highlight_color', val); }, 500) ); edit_visible.addEventListener('change', () => { const val = edit_visible.checked; updateSettings('visible', val); }); const updateSettings = async (type, data) => { const resData = await ( await fetch(this.appRoutes.updateSettings, { method: 'POST', body: JSON.stringify({ type, data }), credentials: 'include', headers: { 'Content-Type': 'application/json', }, }) ).json(); if (resData.success) { this.friends_settings[type] = data; } }; }, openProfileEditor() { let that = this; const overlay = document.createElement('div'); overlay.classList.add('mod_overlay'); overlay.style.opacity = '0'; overlay.innerHTML = ` `; document.body.append(overlay); function hide() { overlay.style.opacity = '0'; setTimeout(() => { overlay.remove(); }, 300); } overlay.addEventListener('click', (e) => { if (e.target == overlay) hide(); }); setTimeout(() => { overlay.style.opacity = '1'; }); byId('closeProfileEditor').addEventListener('click', hide); let changes = new Set(); // Use a Set to store unique changes const bio_edit = byId('bio_edit'); const charCountSpan = byId('charCount'); bio_edit.addEventListener('input', updCharcounter); function updCharcounter() { const currentTextLength = bio_edit.value.length; charCountSpan.textContent = currentTextLength + '/250'; // Update changes changes.add('bio'); } // Upload avatar byId('imageUpload').addEventListener('input', async (e) => { const file = e.target.files[0]; if (!file) return; const formData = new FormData(); formData.append('image', file); try { const data = await ( await fetch(this.appRoutes.imgUpload, { method: 'POST', credentials: 'include', body: formData, }) ).json(); if (data.success) { const profileImg = document.querySelector('.profile-img img'); profileImg.src = data.user.imageURL; hide(); that.profile = data.user; } else { that.modAlert(data.message, 'danger'); console.error('Failed to upload image'); } } catch (error) { console.error('Error uploading image:', error); } }); const username_edit = byId('username_edit'); username_edit.addEventListener('input', () => { changes.add('username'); }); const saveChanges = byId('saveChanges'); saveChanges.addEventListener('click', async () => { if (changes.size === 0) return; let changedData = {}; changes.forEach((change) => { if (change === 'username') { changedData.username = username_edit.value; } else if (change === 'bio') { changedData.bio = bio_edit.value; } }); const resData = await ( await fetch(this.appRoutes.editProfile, { method: 'POST', body: JSON.stringify({ changes: Array.from(changes), data: changedData, }), credentials: 'include', headers: { 'Content-Type': 'application/json', }, }) ).json(); if (resData.success) { if (that.profile.username !== resData.user.username) { const p_username = byId('profile_username_00'); if (p_username) p_username.innerText = resData.user.username; } that.profile = resData.user; changes.clear(); hide(); const name = byId('my-profile-name'); const bioText = byId('my-profile-bio'); name.innerText = resData.user.username; bioText.innerHTML = resData.user.bio || 'No bio.'; } else { if (resData.message) { this.modAlert(resData.message, 'danger'); } } }); const deleteAvatar = byId('deleteAvatar'); deleteAvatar.addEventListener('click', async () => { if ( confirm( 'Are you sure you want to remove your profile picture? It will be changed to the default profile picture.' ) ) { try { const data = await ( await fetch(this.appRoutes.delProfile, { credentials: 'include', }) ).json(); const profileImg = document.querySelector('.profile-img img'); profileImg.src = data.user.imageURL; hide(); that.profile = data.user; } catch (error) { console.error("Couldn't remove image: ", error); this.modAlert(error.message, 'danger'); } } }); }, async announcements() { const previewContainer = byId('mod-announcements'); const announcements = await ( await fetch(this.appRoutes.announcements) ).json(); if (!announcements.success) return; const { data } = announcements; const pinnedAnnouncements = data.filter( (announcement) => announcement.pinned ); const unpinnedAnnouncements = data.filter( (announcement) => !announcement.pinned ); pinnedAnnouncements.sort( (a, b) => new Date(b.date) - new Date(a.date) ); unpinnedAnnouncements.sort( (a, b) => new Date(b.date) - new Date(a.date) ); const sortedAnnouncements = [ ...pinnedAnnouncements, ...unpinnedAnnouncements, ]; const previews = sortedAnnouncements .map( (announcement) => `
${announcement.title}
${announcement.description}
${ announcement.pinned ? '' : '' }
` ) .join(''); previewContainer.innerHTML = previews; const announcementElements = document.querySelectorAll('.mod-announcement'); announcementElements.forEach((element) => { element.addEventListener('click', async (event) => { const announcementId = element.getAttribute( 'data-announcement-id' ); try { const data = await ( await fetch( this.appRoutes.announcement(announcementId) ) ).json(); if (data.success) { createAnnouncementTab(data.data); } } catch (error) { console.error( 'Error fetching announcement details:', error ); } }); }); function createAnnouncementTab(data) { const menuContent = document.querySelector('.mod_menu_content'); const modHome = byId('mod_home'); const content = document.createElement('div'); content.setAttribute('id', 'announcement-tab'); content.classList.add('mod_tab'); content.style.display = 'none'; content.style.opacity = '0'; const announcementHTML = `

${data.full.title}

${prettyTime.fullDate(data.date)}
${data.full.description}
${data.full.images .map( (image) => `` ) .join('')}
`; content.innerHTML = announcementHTML; menuContent.appendChild(content); window.openModTab('announcement-tab'); const back = byId('mod-announcement-back'); back.addEventListener('click', () => { const mod_home = byId('mod_home'); content.style.opacity = '0'; setTimeout(() => { content.remove(); mod_home.style.display = 'flex'; mod_home.style.opacity = '1'; }, 300); byId('tab_home_btn').classList.add('mod_selected'); }); 1; } }, chart() { const canvas = byId('sigmod-stats'); const { Chart } = window; const stats = JSON.parse(localStorage.getItem('game-stats')); // max values const statValues = { timeplayed: [ 10_000, 50_000, 100_000, 150_000, 200_000, 250_000, 300_000, 400_000, 800_000, 1_000_000, ], highestmass: [ 50_000, 100_000, 500_000, 700_000, 1_000_000, 5_000_000, 10_000_000, ], totaldeaths: [ 100, 500, 1_000, 2_000, 3_000, 4_000, 5_000, 8_000, 10_000, 30_000, 50_000, 100_000, ], totalmass: [ 100_000, 500_000, 1_000_000, 2_000_000, 3_000_000, 5_000_000, 10_000_000, 50_000_000, 100_000_000, 500_000_000, 1_000_000_000, 2_000_000_000, 5_000_000_000, ], }; const getBestValue = (stat, values) => { for (let value of values) { if (stat < value) return value; } return values[values.length - 1]; }; const emojiLabels = ['ā²', 'šŸ†', 'šŸ’€', 'šŸ”¢']; const textLabels = [ 'Time Played', 'Highest Mass', 'Total Deaths', 'Total Mass', ]; const data = { labels: emojiLabels, datasets: [ { label: 'Your Stats', data: [ stats['time-played'] / getBestValue( stats['time-played'], statValues.timeplayed ), stats['highest-mass'] / getBestValue( stats['highest-mass'], statValues.highestmass ), stats['total-deaths'] / getBestValue( stats['total-deaths'], statValues.totaldeaths ), stats['total-mass'] / getBestValue( stats['total-mass'], statValues.totalmass ), ], backgroundColor: [ 'rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)', 'rgba(255, 206, 86, 0.2)', 'rgba(153, 102, 255, 0.2)', ], borderColor: [ 'rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(153, 102, 255, 1)', ], borderWidth: 1, }, ], }; const formatLabel = (labelType, actualValue) => { if (labelType === 'Time Played') { const hours = Math.floor(actualValue / 3600); const minutes = Math.floor((actualValue % 3600) / 60); return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`; } else if ( labelType === 'Highest Mass' || labelType === 'Total Mass' ) { return actualValue > 999 ? `${(actualValue / 1000).toFixed(1)}k` : actualValue.toString(); } else { return actualValue.toString(); } }; const createChart = () => { try { new Chart(canvas, { type: 'bar', data: data, options: { indexAxis: 'y', scales: { x: { beginAtZero: true, max: 1, ticks: { callback: (value) => `${(value * 100).toFixed(0)}%`, }, }, }, plugins: { legend: { display: false, }, tooltip: { callbacks: { title: (context) => textLabels[context[0].dataIndex], label: (context) => { const dataIndex = context.dataIndex; const labelType = textLabels[dataIndex]; const actualValue = context.dataset.data[ dataIndex ] * getBestValue( stats[ labelType .toLowerCase() .replace(' ', '-') ], statValues[ labelType .toLowerCase() .replace(' ', '') ] ); return formatLabel( labelType, actualValue ); }, }, }, }, }, }); } catch (error) { console.error( 'An error occurred while rendering the chart:', error ); } }; createChart(); }, // Color input events & Reset color event handler colorPicker() { const colorPickerConfig = { mapColor: { path: 'game.map.color', opacity: false, color: modSettings.game.map.color, default: '#111111', }, borderColor: { path: 'game.borderColor', opacity: true, color: modSettings.game.borderColor, default: '#0000ff', }, foodColor: { path: 'game.foodColor', opacity: true, color: modSettings.game.foodColor, default: null, }, cellColor: { path: 'game.cellColor', opacity: true, color: modSettings.game.cellColor, default: null, }, nameColor: { path: 'game.name.color', opacity: false, color: modSettings.game.name.color, default: '#ffffff', }, gradientNameColor1: { path: 'game.name.gradient.left', opacity: false, color: modSettings.game.name.gradient.left, default: '#ffffff', }, gradientNameColor2: { path: 'game.name.gradient.right', opacity: false, color: modSettings.game.name.gradient.right, default: '#ffffff', }, chatBackground: { path: 'chat.bgColor', opacity: true, color: modSettings.chat.bgColor, default: defaultSettings.chat.bgColor, elementTarget: { selector: '.modChat', property: 'background', }, }, chatTextColor: { path: 'chat.textColor', opacity: true, color: modSettings.chat.textColor, default: defaultSettings.chat.textColor, elementTarget: { selector: '.chatMessage-text', property: 'color', }, }, chatThemeChanger: { path: 'chat.themeColor', opacity: true, color: modSettings.chat.themeColor, default: defaultSettings.chat.themeColor, elementTarget: { selector: '.chatButton', property: 'background', }, }, }; const { Alwan } = window; Object.entries(colorPickerConfig).forEach( ([ selector, { path, opacity, color, default: defaultColor, elementTarget, }, ]) => { const storagePath = path.split('.'); const colorPickerInstance = new Alwan(`#${selector}`, { id: `edit-${selector}`, color: color || defaultColor || '#000000', theme: 'dark', opacity, format: 'hex', default: defaultColor, swatches: ['black', 'white', 'red', 'blue', 'green'], }); const pickerElement = byId(`edit-${selector}`); pickerElement.insertAdjacentHTML( 'beforeend', `
Reset Color
` ); colorPickerInstance.on('change', (e) => { let storageElement = modSettings; storagePath .slice(0, -1) .forEach( (part) => (storageElement = storageElement[part]) ); storageElement[storagePath.at(-1)] = e.hex; if ( path.includes('gradientName') && modSettings.game.name.gradient.enabled ) { modSettings.game.name.gradient.enabled = true; } if (elementTarget) { const targets = document.querySelectorAll( elementTarget.selector ); targets.forEach((target) => { target.style[elementTarget.property] = e.hex; }); } updateStorage(); }); byId(`reset-${selector}`)?.addEventListener('click', () => { colorPickerInstance.setColor(defaultColor); let storageElement = modSettings; storagePath .slice(0, -1) .forEach( (part) => (storageElement = storageElement[part]) ); storageElement[storagePath.at(-1)] = defaultColor; if ( path.includes('gradientName') && !modSettings.game.name.gradient.left && !modSettings.game.name.gradient.right ) { modSettings.game.name.gradient.enabled = false; } if (elementTarget) { const targets = document.querySelectorAll( elementTarget.selector ); targets.forEach((target) => { target.style[elementTarget.property] = defaultColor; }); } updateStorage(); }); } ); }, async getBlockedChatData() { try { const res = await fetch(`${this.appRoutes.blockedChatData}?v=${Math.floor(Math.random() * 9e5)}`, { headers: { 'Content-Type': 'application/json' } }); const resData = await res.json(); const { names, messages } = resData; this.blockedChatData = { names, messages } } catch(e) { console.error('Couldn\'t fetch blocked chat data.'); } }, async loadLibraries() { const loadScript = (src) => new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = src; script.type = 'text/javascript'; document.head.appendChild(script); script.onload = () => resolve(); script.onerror = (error) => reject(error); }); const loadCSS = (href) => new Promise((resolve, reject) => { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = href; document.head.appendChild(link); link.onload = () => resolve(); link.onerror = (error) => reject(error); }); for (const [lib, val] of Object.entries(libs)) { if (typeof val === 'string') { await loadScript(val); } else { await loadScript(val.js); if (val.css) await loadCSS(val.css); } console.log(`%cāœ… Loaded ${lib}.`, 'color: lime'); if (typeof this[lib] === 'function') this[lib](); } }, isRateLimited() { if (document.body.children[0]?.id === 'cf-wrapper') { console.log('User is rate limited.'); return true; } return false; }, setupUI() { this.menu(); this.initStats(); this.announcements(); this.mainMenu(); this.saveNames(); this.tagsystem(); this.createMinimap(); this.themes(); }, setupGame() { this.game(); this.macros(); }, setupNetworking() { this.clientPing(); this.chat(); this.handleNick(); }, initModules() { try { this.loadLibraries(); this.setupUI(); this.setupGame(); this.setupNetworking(); // setup eventListeners for modInputs once every module has been loaded this.setInputActions(); } catch (e) { console.error('An error occurred while loading SigMod: ', e); } }, init() { if (this.isRateLimited()) return; if (!document.querySelector('.body__inner')) return; this.credits(); new SigWsHandler(); this.initModules(); } }; mods = new Mod(); })();