Advanced customization with full ASCII art support. Optimized for performance. Auto-clear on exit.
// ==UserScript==
// @name Rie's Mod
// @namespace https://github.com/khayrie
// @version 4.5
// @description Advanced customization with full ASCII art support. Optimized for performance. Auto-clear on exit.
// @author khayrie
// @match https://bonk.io/*
// @match https://bonkisback.io/*
// @match https://multiplayer.gg/physics/*
// @grant unsafeWindow
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
let CUSTOM_NAME = "★Rie★";
let isNameActive = true;
let currentGradient = null;
let namePosition = 'normal';
let namesVisible = true;
let rainbowSpeed = 0;
let glowColor = null;
let nameScale = 1.0;
let shakeEnabled = false;
let afkStatus = null;
let currentTheme = 'default';
let gameReady = false;
let mutationObserver = null;
let emoteUpdateTimer = null;
let lastUpdate = 0;
const nicknames = {};
const customLevels = {};
const friendsList = new Set();
const emoteQueue = [];
const uw = unsafeWindow;
const OWNER_USERNAMES = new Set(["ki1la", "khayrie", "Il fait"]);
const OWNER_BADGE_HTML = `<span title="Owner" style="color: gold; font-weight: bold; margin-left: 4px;">★</span>`;
const THEMES = {
default: { chatBg: 'rgba(0,0,0,0.7)', chatText: '#FFFFFF', scoreboardBg: 'rgba(0,0,0,0.8)', scoreboardText: '#FFFFFF', accent: '#FFD700' },
dark: { chatBg: 'rgba(20,20,30,0.9)', chatText: '#E0E0FF', scoreboardBg: 'rgba(30,30,40,0.9)', scoreboardText: '#E0E0FF', accent: '#7B68EE' },
neon: { chatBg: 'rgba(0,0,0,0.9)', chatText: '#00FF00', scoreboardBg: 'rgba(0,0,0,0.95)', scoreboardText: '#00FF00', accent: '#FF00FF' },
sunset: { chatBg: 'rgba(30,20,40,0.85)', chatText: '#FFD700', scoreboardBg: 'rgba(40,25,50,0.9)', scoreboardText: '#FFA500', accent: '#FF4500' },
ocean: { chatBg: 'rgba(10,25,40,0.85)', chatText: '#1E90FF', scoreboardBg: 'rgba(15,30,45,0.9)', scoreboardText: '#87CEEB', accent: '#00BFFF' }
};
const EMOTES = {
heart: '❤️', fire: '🔥', star: '⭐', laugh: '😂', cry: '😢', angry: '😠', cool: '😎', party: '🎉',
poop: '💩', rocket: '🚀', skull: '💀', thumbsup: '👍', thumbsdown: '👎', clap: '👏', money: '💰',
pizza: '🍕', trophy: '🏆'
};
const NAME_SELECTORS = [
'#pretty_top_name', '.newbonklobby_playerentry_name', '.ingamescoreboard_playername',
'.ingamechatname', '.newbonklobby_chat_msg_name', '#ingamewinner_top', '.replay_playername'
];
const LEVEL_SELECTORS = [
'#pretty_top_level', '.newbonklobby_playerentry_level', '.ingamescoreboard_playerlevel'
];
const UPDATE_INTERVAL = 200;
const EMOTE_UPDATE_INTERVAL = 50;
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function parseQuotedArgs(input) {
const tokens = [];
let current = '';
let inQuote = false;
let escapeNext = false;
for (let i = 0; i < input.length; i++) {
const char = input[i];
if (escapeNext) { current += char; escapeNext = false; continue; }
if (char === '\\') { escapeNext = true; continue; }
if (char === '"' && (i === 0 || input[i-1] !== '\\')) { inQuote = !inQuote; continue; }
if (char === ' ' && !inQuote) { if (current !== '') { tokens.push(current); current = ''; } continue; }
current += char;
}
if (current !== '') tokens.push(current);
return tokens;
}
function applyNamePosition(el) {
if (!el || !el.style) return;
el.style.textAlign = '';
el.style.position = '';
el.style.top = '';
el.style.transform = '';
switch(namePosition) {
case 'left': el.style.textAlign = 'left'; break;
case 'right': el.style.textAlign = 'right'; break;
case 'up': el.style.position = 'relative'; el.style.top = '-8px'; el.style.display = 'block'; break;
default: if (el.classList && el.classList.contains('ingamescoreboard_playername')) el.style.textAlign = 'center'; break;
}
}
function applyVisualEffects(el, isSelf = false) {
if (!el || !el.style) return;
if (isSelf && nameScale !== 1.0) { el.style.transform = `scale(${nameScale})`; el.style.display = 'inline-block'; el.style.transformOrigin = 'left center'; }
if (isSelf && shakeEnabled) { el.style.animation = 'shake 0.1s infinite'; }
if (isSelf && glowColor) { el.style.textShadow = `0 0 8px ${glowColor}, 0 0 16px ${glowColor}`; }
}
function waitForGame(callback, retries = 0) {
if (retries > 50) return;
const frame = document.getElementById('maingameframe');
if (!frame || !frame.contentWindow) { setTimeout(() => waitForGame(callback, retries + 1), 200); return; }
const gameWin = frame.contentWindow;
const gameDoc = frame.contentDocument;
if (!gameWin.PIXI || typeof uw.playerids === 'undefined' || typeof uw.myid === 'undefined' || !gameDoc.body) {
setTimeout(() => waitForGame(callback, retries + 1), 200);
return;
}
gameReady = true;
callback(gameWin, gameDoc);
}
function createFlowingGradient(colors, progress) {
const stops = colors.map((color, i) => { const pos = (i / (colors.length - 1)) * 100; return `${color} ${pos}%`; }).join(', ');
return `linear-gradient(${progress}deg, ${stops})`;
}
function applyGradientEffect(el, gradient) {
if (!gradient || !el || !el.style) return;
el.dataset.gradientApplied = 'true';
let progress = Math.random() * 360;
if (el.dataset.gradientInterval) { clearInterval(parseInt(el.dataset.gradientInterval)); }
const interval = setInterval(() => {
if (!el || !el.isConnected || !el.style) { clearInterval(interval); return; }
progress = (progress + 1) % 360;
try {
el.style.backgroundImage = createFlowingGradient(gradient.colors, progress);
el.style.backgroundClip = "text";
el.style.webkitBackgroundClip = "text";
el.style.color = "transparent";
} catch (e) {}
}, gradient.speed);
el.dataset.gradientInterval = interval.toString();
}
function hookPIXIText(gameWin) {
if (!gameWin?.PIXI?.Text?.prototype) return;
const originalUpdate = gameWin.PIXI.Text.prototype.updateText;
gameWin.PIXI.Text.prototype.updateText = function() {
try {
if (typeof this.text !== 'string') return originalUpdate.call(this);
if (!namesVisible) { this.text = ""; return originalUpdate.call(this); }
const playerIds = uw.playerids || {};
const myId = uw.myid;
for (const id in playerIds) {
const player = playerIds[id];
if (!player || !player.userName) continue;
let displayName = player.userName;
if (nicknames[id]) { displayName = nicknames[id]; }
else if (id == myId && isNameActive) { displayName = CUSTOM_NAME; }
if (displayName === player.userName) continue;
const safeName = escapeRegExp(player.userName);
if (new RegExp(safeName, 'i').test(this.text)) {
this.text = this.text.replace(new RegExp(safeName, 'ig'), displayName);
if (id == myId && isNameActive && rainbowSpeed > 0 && gameWin.PIXI?.utils?.rgb2hex) {
const hue = (Date.now() / rainbowSpeed) % 360;
this.style.fill = gameWin.PIXI.utils.rgb2hex([
Math.sin(hue * Math.PI / 180),
Math.sin((hue + 120) * Math.PI / 180),
Math.sin((hue + 240) * Math.PI / 180)
]);
}
}
}
} catch (e) {}
return originalUpdate.call(this);
};
}
function injectOwnerBadges(doc) {
if (!doc || typeof uw.myid === 'undefined' || typeof uw.playerids === 'undefined') return;
if (OWNER_USERNAMES.has(uw.playerids[uw.myid]?.userName)) return;
try {
const nameElements = doc.querySelectorAll(
'.newbonklobby_playerentry_name, .ingamescoreboard_playername, .ingamechatname, ' +
'.newbonklobby_chat_msg_name, #ingamewinner_top, .replay_playername'
);
for (let i = 0; i < nameElements.length; i++) {
const el = nameElements[i];
const existingBadge = el.nextElementSibling;
if (existingBadge && existingBadge.innerHTML.includes('★')) { existingBadge.remove(); }
for (const ownerName of OWNER_USERNAMES) {
if (el.textContent && el.textContent.trim() === ownerName) {
const badge = doc.createElement('span');
badge.innerHTML = OWNER_BADGE_HTML;
badge.style.display = 'inline';
el.parentNode.insertBefore(badge, el.nextSibling);
break;
}
}
}
} catch (e) {}
}
function loadFriends() {
try {
const saved = localStorage.getItem('bonk_friends');
if (saved) { JSON.parse(saved).forEach(name => friendsList.add(name)); }
} catch (e) {}
}
function saveFriends() {
try { localStorage.setItem('bonk_friends', JSON.stringify(Array.from(friendsList))); } catch (e) {}
}
function clearAllDataExceptFriends() {
// Clear all transient data
CUSTOM_NAME = "★Rie★";
isNameActive = true;
currentGradient = null;
namePosition = 'normal';
namesVisible = true;
rainbowSpeed = 0;
glowColor = null;
nameScale = 1.0;
shakeEnabled = false;
afkStatus = null;
currentTheme = 'default';
Object.keys(nicknames).forEach(k => delete nicknames[k]);
Object.keys(customLevels).forEach(k => delete customLevels[k]);
emoteQueue.length = 0;
lastUpdate = 0;
}
function isFriend(playerId) {
const player = uw.playerids?.[playerId];
if (!player || !player.userName) return false;
return friendsList.has(player.userName);
}
function getFriendBadge() {
return `<span style="color: #1E90FF; font-weight: bold; margin-left: 2px;">💙</span>`;
}
function addEmote(playerId, emoji) {
emoteQueue.push({ playerId: playerId, emoji: emoji, timestamp: Date.now() });
}
function updateEmotes(gameDoc) {
if (!gameDoc || emoteQueue.length === 0 || !uw.playerids) return;
const now = Date.now();
const playerIds = uw.playerids;
const gameScale = uw.gamescale;
const screenOffsetX = uw.screenoffsetx;
const screenOffsetY = uw.screenoffsety;
for (let i = emoteQueue.length - 1; i >= 0; i--) {
const emote = emoteQueue[i];
const age = now - emote.timestamp;
if (age > 3000) {
if (emote.element && emote.element.parentNode) {
try { emote.element.parentNode.removeChild(emote.element); } catch (e) {}
}
emoteQueue.splice(i, 1);
continue;
}
const player = playerIds[emote.playerId];
if (!player || player.x === undefined || player.y === undefined || !gameScale || !screenOffsetX) continue;
try {
const screenX = player.x * gameScale + screenOffsetX;
const screenY = player.y * gameScale + screenOffsetY - 30;
if (!emote.element) {
emote.element = gameDoc.createElement('div');
emote.element.style.position = 'absolute';
emote.element.style.zIndex = '10000';
emote.element.style.fontSize = '28px';
emote.element.style.fontWeight = 'bold';
emote.element.style.pointerEvents = 'none';
emote.element.style.textShadow = '0 0 5px black, 0 0 10px black, 0 0 15px rgba(0,0,0,0.8)';
emote.element.style.color = 'white';
emote.element.style.fontFamily = 'Arial, sans-serif';
emote.element.style.left = `${screenX - 14}px`;
emote.element.style.top = `${screenY}px`;
emote.element.textContent = emote.emoji;
gameDoc.body.appendChild(emote.element);
}
const progress = age / 3000;
const opacity = 1 - progress;
const offsetY = -progress * 60;
emote.element.style.left = `${screenX - 14}px`;
emote.element.style.top = `${screenY + offsetY}px`;
emote.element.style.opacity = opacity;
emote.element.style.transform = `scale(${1 + progress * 0.3})`;
} catch (e) {}
}
}
function getPlayerIdFromLevelElement(el) {
if (!el || !uw.playerids) return null;
let parent = el.parentElement;
let nameElement = null;
while (parent && !nameElement) {
try {
nameElement = parent.querySelector('.newbonklobby_playerentry_name, .ingamescoreboard_playername, .ingamechatname');
} catch (e) {}
if (nameElement) break;
parent = parent.parentElement;
}
if (!nameElement || !nameElement.textContent) return null;
const nameText = nameElement.textContent.trim();
const playerIds = uw.playerids;
for (const id in playerIds) {
const player = playerIds[id];
if (!player || !player.userName) continue;
let displayName = player.userName;
if (nicknames[id]) { displayName = nicknames[id]; }
else if (id == uw.myid && isNameActive) { displayName = CUSTOM_NAME; }
if (displayName === nameText) { return id; }
}
return null;
}
function applyTheme(themeName) {
const theme = THEMES[themeName] || THEMES.default;
try {
document.documentElement.style.setProperty('--chat-bg', theme.chatBg);
document.documentElement.style.setProperty('--chat-text', theme.chatText);
document.documentElement.style.setProperty('--scoreboard-bg', theme.scoreboardBg);
document.documentElement.style.setProperty('--scoreboard-text', theme.scoreboardText);
document.documentElement.style.setProperty('--accent', theme.accent);
} catch (e) {}
const doc = uw.Gdocument;
if (!doc) return;
try {
const chatContainers = doc.querySelectorAll('.newbonklobby_chat_container, .ingamechatcontainer');
for (let i = 0; i < chatContainers.length; i++) {
chatContainers[i].style.backgroundColor = theme.chatBg;
}
const chatTexts = doc.querySelectorAll('.newbonklobby_chat_msg_text, .ingamechatmsgtext');
for (let i = 0; i < chatTexts.length; i++) {
chatTexts[i].style.color = theme.chatText;
}
const scoreboards = doc.querySelectorAll('.ingamescoreboard');
for (let i = 0; i < scoreboards.length; i++) {
scoreboards[i].style.backgroundColor = theme.scoreboardBg;
}
const scoreboardTexts = doc.querySelectorAll('.ingamescoreboard_playername, .ingamescoreboard_playerlevel');
for (let i = 0; i < scoreboardTexts.length; i++) {
scoreboardTexts[i].style.color = theme.scoreboardText;
}
} catch (e) {}
}
function forceInstantUpdate(doc) {
if (!doc) doc = uw.Gdocument;
if (!doc || !gameReady) return;
try {
const targets = NAME_SELECTORS.concat(LEVEL_SELECTORS);
for (let i = 0; i < targets.length; i++) {
const selector = targets[i];
const elements = doc.querySelectorAll(selector);
for (let j = 0; j < elements.length; j++) {
const el = elements[j];
delete el.dataset.customProcessed;
delete el.dataset.ownerBadgeProcessed;
delete el.dataset.gradientApplied;
if (el.style) {
el.style.transform = '';
el.style.animation = '';
el.style.textShadow = '';
el.style.textAlign = '';
el.style.position = '';
el.style.top = '';
el.style.backgroundImage = '';
el.style.backgroundClip = '';
el.style.webkitBackgroundClip = '';
el.style.color = '';
}
}
}
updateAllDOM(doc);
} catch (e) {}
}
function updateAllDOM(doc) {
if (!doc || !gameReady || !uw.playerids) return;
const now = Date.now();
if (now - lastUpdate < UPDATE_INTERVAL) return;
lastUpdate = now;
try {
const playerIds = uw.playerids;
const myId = uw.myid;
const nameElements = [];
const levelElements = [];
for (let i = 0; i < NAME_SELECTORS.length; i++) {
const els = doc.querySelectorAll(NAME_SELECTORS[i]);
for (let j = 0; j < els.length; j++) {
nameElements.push(els[j]);
}
}
for (let i = 0; i < LEVEL_SELECTORS.length; i++) {
const els = doc.querySelectorAll(LEVEL_SELECTORS[i]);
for (let j = 0; j < els.length; j++) {
levelElements.push(els[j]);
}
}
for (let i = 0; i < nameElements.length; i++) {
const el = nameElements[i];
applyNamePosition(el);
if (!namesVisible) { el.textContent = ""; continue; }
for (const id in playerIds) {
const player = playerIds[id];
if (!player || !player.userName) continue;
const safeName = escapeRegExp(player.userName);
let displayValue = player.userName;
if (nicknames[id]) { displayValue = nicknames[id]; }
else if (id == myId && isNameActive) { displayValue = CUSTOM_NAME; }
if (el.textContent && new RegExp(safeName, 'i').test(el.textContent)) {
el.textContent = el.textContent.replace(new RegExp(safeName, 'ig'), displayValue);
if (id == myId && isNameActive && currentGradient) { applyGradientEffect(el, currentGradient); }
if (id == myId && isNameActive) { applyVisualEffects(el, true); }
if (isFriend(id)) {
let badge = el.nextElementSibling;
while (badge && badge.innerHTML && badge.innerHTML.includes('💙')) {
const next = badge.nextElementSibling;
try { badge.remove(); } catch (e) {}
badge = next;
}
badge = doc.createElement('span');
badge.innerHTML = getFriendBadge();
badge.style.display = 'inline';
el.parentNode.insertBefore(badge, el.nextSibling);
} else {
let badge = el.nextElementSibling;
while (badge && badge.innerHTML && badge.innerHTML.includes('💙')) {
const next = badge.nextElementSibling;
try { badge.remove(); } catch (e) {}
badge = next;
}
}
}
applyNamePosition(el);
}
}
for (let i = 0; i < levelElements.length; i++) {
const el = levelElements[i];
const playerId = getPlayerIdFromLevelElement(el);
if (playerId && customLevels[playerId]) { el.textContent = customLevels[playerId]; }
else if (playerId == myId && customLevels[myId]) { el.textContent = customLevels[myId]; }
}
injectOwnerBadges(doc);
updateEmotes(doc);
} catch (e) {}
}
function broadcastCustomization() {
if (!uw.sendToServer || !gameReady) return;
try {
uw.sendToServer(JSON.stringify({
type: "bonk_customizer",
name: CUSTOM_NAME,
level: customLevels[uw.myid] || "Level 1",
gradient: currentGradient,
namePosition: namePosition,
namesVisible: namesVisible,
rainbowSpeed: rainbowSpeed,
glowColor: glowColor,
nameScale: nameScale,
shakeEnabled: shakeEnabled,
afkStatus: afkStatus
}));
} catch (e) {}
}
function broadcastNickname(playerId, nickname) {
if (!uw.sendToServer || !playerId || !gameReady) return;
try { uw.sendToServer(JSON.stringify({ type: "bonk_nick", targetId: playerId, nickname: nickname })); } catch (e) {}
}
function broadcastLevel(playerId, levelStr) {
if (!uw.sendToServer || !playerId || !gameReady) return;
try { uw.sendToServer(JSON.stringify({ type: "bonk_level", targetId: playerId, level: levelStr })); } catch (e) {}
}
function broadcastEmote(emoji) {
if (!uw.sendToServer || !gameReady) return;
try { uw.sendToServer(JSON.stringify({ type: "bonk_emote", senderId: uw.myid, emoji: emoji })); } catch (e) {}
}
function broadcastClap(targetId) {
if (!uw.sendToServer || !gameReady) return;
try { uw.sendToServer(JSON.stringify({ type: "bonk_clap", senderId: uw.myid, targetId: targetId })); } catch (e) {}
}
function broadcastClearData() {
if (!uw.sendToServer || !gameReady) return;
try {
uw.sendToServer(JSON.stringify({ type: "bonk_clearnicks", senderId: uw.myid }));
uw.sendToServer(JSON.stringify({ type: "bonk_clearlevel", targetId: uw.myid }));
} catch (e) {}
}
function sendPrivateMessage(targetId, message) {
if (!uw.sendToServer || !gameReady) return;
try {
uw.sendToServer(JSON.stringify({
type: "bonk_pm",
targetId: targetId,
senderId: uw.myid,
senderName: getDisplayName(uw.myid),
content: message,
afkStatus: afkStatus
}));
} catch (e) {}
}
function getDisplayName(playerId) {
if (!namesVisible) return "";
if (nicknames[playerId]) return nicknames[playerId];
if (playerId == uw.myid && isNameActive) return CUSTOM_NAME;
return uw.playerids?.[playerId]?.userName || "Guest";
}
function handleCustomMessage(data) {
try {
const msg = JSON.parse(data);
if (!msg.senderId) return;
switch(msg.type) {
case "bonk_customizer":
if (!uw.remoteCustomizations) uw.remoteCustomizations = {};
uw.remoteCustomizations[msg.senderId] = {
name: msg.name, level: msg.level, gradient: msg.gradient, namePosition: msg.namePosition,
namesVisible: msg.namesVisible, rainbowSpeed: msg.rainbowSpeed, glowColor: msg.glowColor,
nameScale: msg.nameScale, shakeEnabled: msg.shakeEnabled, afkStatus: msg.afkStatus
};
if (msg.senderId == uw.myid) {
namePosition = msg.namePosition || 'normal';
namesVisible = msg.namesVisible !== undefined ? msg.namesVisible : true;
rainbowSpeed = msg.rainbowSpeed || 0;
glowColor = msg.glowColor || null;
nameScale = msg.nameScale || 1.0;
shakeEnabled = msg.shakeEnabled || false;
afkStatus = msg.afkStatus || null;
}
forceInstantUpdate(uw.Gdocument);
break;
case "bonk_nick":
if (msg.targetId) {
if (msg.nickname === "" || msg.nickname === null) { delete nicknames[msg.targetId]; }
else { nicknames[msg.targetId] = msg.nickname; }
forceInstantUpdate(uw.Gdocument);
}
break;
case "bonk_level":
if (msg.targetId && msg.level) {
customLevels[msg.targetId] = msg.level;
forceInstantUpdate(uw.Gdocument);
}
break;
case "bonk_pm":
if (msg.targetId === uw.myid && msg.senderName && msg.content) {
const displayMsg = namesVisible ? `[PM from ${msg.senderName}] ${msg.content}` : `[PM] ${msg.content}`;
if (afkStatus && msg.senderId !== uw.myid) { sendPrivateMessage(msg.senderId, `I'm currently AFK: ${afkStatus}`); }
if (uw.displayInChat) uw.displayInChat(displayMsg, "#00FF00", "#00AA00");
}
break;
case "bonk_clearnicks":
if (msg.senderId && msg.senderId !== uw.myid) return;
Object.keys(nicknames).forEach(id => { broadcastNickname(id, ""); });
Object.keys(nicknames).forEach(id => delete nicknames[id]);
forceInstantUpdate(uw.Gdocument);
break;
case "bonk_clearlevel":
if (msg.targetId) {
delete customLevels[msg.targetId];
forceInstantUpdate(uw.Gdocument);
}
break;
case "bonk_emote":
if (msg.senderId && msg.emoji) { addEmote(msg.senderId, msg.emoji); }
break;
case "bonk_clap":
if (msg.senderId && msg.targetId) {
if (msg.targetId === uw.myid) { addEmote(uw.myid, '👏'); }
addEmote(msg.senderId, '👏');
}
break;
}
} catch (e) {}
}
function findPlayerIdByName(namePart) {
if (!namePart || !uw.playerids) return null;
const cleanPart = namePart.toLowerCase().replace(/^"|"$/g, '').trim();
const playerIds = uw.playerids;
for (const id in playerIds) {
const player = playerIds[id];
if (player && player.userName && player.userName.toLowerCase().includes(cleanPart)) { return id; }
}
return null;
}
function showHelp() {
const helpLines = [
"╔════════════════════════════════════════════════╗",
"║ Rie's Mod v4.5-ascii - Command List ║",
"╠════════════════════════════════════════════════╣",
"║ 🎨 VISUAL EFFECTS ║",
"║ /rainbow <speed> Rainbow text (10-1000) ║",
"║ /glow <color> Text glow effect ║",
"║ /scale <size> Scale name (1.0-2.0) ║",
"║ /shake Toggle wobble effect ║",
"║ /theme <preset> UI themes ║",
"║ ║",
"║ 👥 SOCIAL ║",
"║ /whois <player> Show real username ║",
"║ /friends add <plr> Add friend ║",
"║ /afk <reason> Set AFK status ║",
"║ /emote <type> Show emoji above head ║",
"║ /clap <player> Send clap animation ║",
"║ ║",
"║ 🔧 CORE ║",
"║ /name <text> Change your name ║",
"║ (supports ASCII art!) ║",
"║ /level <player> <num> Set player's level ║",
"║ /nick <player> <name> Nickname player ║",
"║ (supports ASCII art!) ║",
"║ /m <player> <msg> Private message ║",
"║ /clearnick Clear all nicknames ║",
"║ /info Show this help ║",
"╚════════════════════════════════════════════════╝"
];
const display = uw.displayInChat;
if (!display) return;
for (let i = 0; i < helpLines.length; i++) {
display(helpLines[i], "#FFD700", "#FFA500");
}
}
function cleanupRoom() {
if (mutationObserver) {
try { mutationObserver.disconnect(); } catch (e) {}
mutationObserver = null;
}
if (emoteUpdateTimer) {
clearInterval(emoteUpdateTimer);
emoteUpdateTimer = null;
}
emoteQueue.length = 0;
gameReady = false;
lastUpdate = 0;
}
function fullCleanup() {
cleanupRoom();
broadcastClearData();
clearAllDataExceptFriends();
}
function addCommands() {
if (typeof uw.commandhandle !== 'function') { setTimeout(addCommands, 500); return; }
const originalCommandHandle = uw.commandhandle;
uw.commandhandle = function(chat_val) {
if (!gameReady) return originalCommandHandle(chat_val);
const display = uw.displayInChat;
if (chat_val.startsWith('/rainbow ')) {
const speed = parseInt(chat_val.substring(9).trim());
if (isNaN(speed) || speed < 10 || speed > 1000) { if (display) display("Speed must be 10-1000.", "#FF0000", "#FF0000"); return ""; }
rainbowSpeed = speed; broadcastCustomization(); forceInstantUpdate(uw.Gdocument);
if (display) display(`🌈 Rainbow effect enabled (speed: ${speed})`, "#00FF00", "#00AA00"); return "";
}
if (chat_val === '/rainbow') { rainbowSpeed = 0; broadcastCustomization(); forceInstantUpdate(uw.Gdocument);
if (display) display("🌈 Rainbow effect disabled", "#FFD700", "#FFA500"); return ""; }
if (chat_val.startsWith('/glow ')) {
const color = chat_val.substring(6).trim();
const test = document.createElement('div');
test.style.color = color;
if (test.style.color === '') { if (display) display("Invalid color.", "#FF0000", "#FF0000"); return ""; }
glowColor = color; broadcastCustomization(); forceInstantUpdate(uw.Gdocument);
if (display) display(`✨ Glow effect enabled (${color})`, "#00FF00", "#00AA00"); return "";
}
if (chat_val === '/glow') { glowColor = null; broadcastCustomization(); forceInstantUpdate(uw.Gdocument);
if (display) display("✨ Glow effect disabled", "#FFD700", "#FFA500"); return ""; }
if (chat_val.startsWith('/scale ')) {
const size = parseFloat(chat_val.substring(7).trim());
if (isNaN(size) || size < 1.0 || size > 2.0) { if (display) display("Scale must be 1.0-2.0.", "#FF0000", "#FF0000"); return ""; }
nameScale = size; broadcastCustomization(); forceInstantUpdate(uw.Gdocument);
if (display) display(`↔️ Name scale set to ${size}x`, "#00FF00", "#00AA00"); return "";
}
if (chat_val === '/shake') {
shakeEnabled = !shakeEnabled; broadcastCustomization(); forceInstantUpdate(uw.Gdocument);
if (display) display(shakeEnabled ? "🌀 Shake effect enabled" : "🌀 Shake effect disabled", "#00FF00", "#00AA00"); return "";
}
if (chat_val.startsWith('/theme ')) {
const theme = chat_val.substring(7).trim().toLowerCase();
if (!THEMES[theme]) { if (display) display(`Invalid theme. Choose: ${Object.keys(THEMES).join(', ')}`, "#FF0000", "#FF0000"); return ""; }
currentTheme = theme; applyTheme(theme);
if (display) display(`🎨 Theme changed to: ${theme}`, "#00FF00", "#00AA00"); return "";
}
if (chat_val.startsWith('/whois ')) {
const playerName = chat_val.substring(7).trim();
const playerId = findPlayerIdByName(playerName);
if (!playerId) { if (display) display(`Player "${playerName}" not found.`, "#FF0000", "#FF0000"); return ""; }
const realName = uw.playerids?.[playerId]?.userName || "Unknown";
const nick = nicknames[playerId] || "None";
if (display) display(`🔍 ${playerName}: Real = "${realName}", Nick = "${nick}"`, "#00FF00", "#00AA00"); return "";
}
if (chat_val.startsWith('/friends add ')) {
const playerName = chat_val.substring(13).trim();
const playerId = findPlayerIdByName(playerName);
if (!playerId) { if (display) display(`Player "${playerName}" not found.`, "#FF0000", "#FF0000"); return ""; }
const realName = uw.playerids?.[playerId]?.userName;
if (!realName) { if (display) display("Could not get player name.", "#FF0000", "#FF0000"); return ""; }
if (friendsList.has(realName)) { if (display) display(`${realName} is already in your friends list.`, "#FF0000", "#FF0000"); return ""; }
friendsList.add(realName); saveFriends(); forceInstantUpdate(uw.Gdocument);
if (display) display(`💙 Added ${realName} to friends`, "#00FF00", "#00AA00"); return "";
}
if (chat_val.startsWith('/afk ')) {
const reason = chat_val.substring(5).trim();
afkStatus = reason; broadcastCustomization();
if (display) display(`💤 AFK: "${reason}" (Auto-reply enabled)`, "#00FF00", "#00AA00"); return "";
}
if (chat_val === '/afk') {
if (afkStatus) { afkStatus = null; broadcastCustomization(); if (display) display("✅ AFK mode disabled", "#FFD700", "#FFA500"); }
else { if (display) display("Usage: /afk <reason>", "#FF0000", "#FF0000"); } return "";
}
if (chat_val.startsWith('/emote ')) {
const type = chat_val.substring(7).trim().toLowerCase();
const emoji = EMOTES[type];
if (!emoji) { if (display) display(`Invalid emote. Choose: ${Object.keys(EMOTES).join(', ')}`, "#FF0000", "#FF0000"); return ""; }
addEmote(uw.myid, emoji); broadcastEmote(emoji);
if (display) display(`💬 Emote sent: ${emoji}`, "#00FF00", "#00AA00"); return "";
}
if (chat_val.startsWith('/clap ')) {
const playerName = chat_val.substring(6).trim();
const playerId = findPlayerIdByName(playerName);
if (!playerId) { if (display) display(`Player "${playerName}" not found.`, "#FF0000", "#FF0000"); return ""; }
addEmote(uw.myid, '👏'); addEmote(playerId, '👏'); broadcastClap(playerId);
const targetName = getDisplayName(playerId);
if (display) display(`👏 Clapped for ${targetName}!`, "#00FF00", "#00AA00"); return "";
}
if (chat_val.startsWith('/level ')) {
const rest = chat_val.substring(7).trim();
const args = parseQuotedArgs(rest);
if (args.length < 2) { if (display) display("Usage: /level <player> <number>", "#FF0000", "#FF0000"); return ""; }
const playerName = args[0];
const levelNum = parseInt(args[1]);
const playerId = findPlayerIdByName(playerName);
if (!playerId) { if (display) display(`Player "${playerName}" not found.`, "#FF0000", "#FF0000"); return ""; }
if (isNaN(levelNum) || levelNum < 0 || levelNum > 9999) { if (display) display("Level must be 0-9999.", "#FF0000", "#FF0000"); return ""; }
const levelStr = `Level ${levelNum}`;
customLevels[playerId] = levelStr; broadcastLevel(playerId, levelStr); forceInstantUpdate(uw.Gdocument);
const targetName = uw.playerids?.[playerId]?.userName || "Player";
if (display) display(`📊 Set ${targetName}'s level to: ${levelStr}`, "#00FF00", "#00AA00"); return "";
}
if (chat_val === '/clearnick') {
Object.keys(nicknames).forEach(id => { broadcastNickname(id, ""); });
Object.keys(nicknames).forEach(id => delete nicknames[id]);
if (uw.sendToServer) { try { uw.sendToServer(JSON.stringify({ type: "bonk_clearnicks", senderId: uw.myid })); } catch (e) {} }
forceInstantUpdate(uw.Gdocument);
if (display) display("🏷️ All nicknames cleared", "#FFD700", "#FFA500"); return "";
}
if (chat_val.startsWith('/nick ')) {
const rest = chat_val.substring(6).trim();
const args = parseQuotedArgs(rest);
if (args.length < 2) { if (display) display('Usage: /nick <player> <nickname>', "#FF0000", "#FF0000"); return ""; }
const playerName = args[0];
const nickname = args.slice(1).join(" ");
const playerId = findPlayerIdByName(playerName);
if (!playerId) { if (display) display(`Player "${playerName}" not found.`, "#FF0000", "#FF0000"); return ""; }
if (nickname.trim().length === 0) { if (display) display("Nickname cannot be empty.", "#FF0000", "#FF0000"); return ""; }
nicknames[playerId] = nickname; broadcastNickname(playerId, nickname); forceInstantUpdate(uw.Gdocument);
if (display) display(`🏷️ Nickname applied`, "#00FF00", "#00AA00"); return "";
}
if (chat_val.startsWith('/m ')) {
const rest = chat_val.substring(3).trim();
const args = parseQuotedArgs(rest);
if (args.length < 2) { if (display) display('Usage: /m <player> <message>', "#FF0000", "#FF0000"); return ""; }
const playerName = args[0];
const message = args.slice(1).join(" ");
const playerId = findPlayerIdByName(playerName);
if (!playerId) { if (display) display(`Player "${playerName}" not found.`, "#FF0000", "#FF0000"); return ""; }
sendPrivateMessage(playerId, message);
if (display) display(`💬 [PM to ${getDisplayName(playerId)}] ${message}`, "#00FF00", "#00AA00"); return "";
}
if (chat_val.startsWith('/namepos ')) {
const pos = chat_val.substring(9).trim().toLowerCase();
if (!['left', 'right', 'up', 'normal'].includes(pos)) { if (display) display("Invalid position. Use: left, right, up, normal", "#FF0000", "#FF0000"); return ""; }
namePosition = pos; broadcastCustomization(); forceInstantUpdate(uw.Gdocument);
if (display) display(`📍 Name position set to: ${pos}`, "#00FF00", "#00AA00"); return "";
}
if (chat_val === '/clearnames') {
namesVisible = false; broadcastCustomization(); forceInstantUpdate(uw.Gdocument);
if (display) display("👻 Player names hidden. Use /shownames to restore.", "#FFD700", "#FFA500"); return "";
}
if (chat_val === '/shownames') {
namesVisible = true; broadcastCustomization(); forceInstantUpdate(uw.Gdocument);
if (display) display("✅ Player names restored", "#FFD700", "#FFA500"); return "";
}
if (chat_val.startsWith('/name ')) {
const newName = chat_val.substring(6);
if (newName.trim().length > 0) {
CUSTOM_NAME = newName; isNameActive = true; broadcastCustomization(); forceInstantUpdate(uw.Gdocument);
if (display) display(`🏷️ Name applied`, "#00FF00", "#00AA00");
} else { if (display) display("Name cannot be empty.", "#FF0000", "#FF0000"); }
return "";
} else if (chat_val === '/name') {
isNameActive = !isNameActive; broadcastCustomization(); forceInstantUpdate(uw.Gdocument);
if (display) display(isNameActive ? "✅ Custom name enabled" : "✅ Custom name disabled", "#FFD700", "#FFA500"); return "";
}
if (chat_val.startsWith('/gradient ')) {
const input = chat_val.substring(10).trim();
const lowerInput = input.toLowerCase();
if (PRESETS && PRESETS[lowerInput]) {
currentGradient = PRESETS[lowerInput];
broadcastCustomization(); forceInstantUpdate(uw.Gdocument);
if (display) display(`🎨 Preset applied: ${lowerInput}`, "#00FF00", "#00AA00"); return "";
}
const args = input.split(',');
if (args.length < 2) { if (display) display("Usage: /gradient color1,color2[,speed] OR preset", "#FF0000", "#FF0000"); return ""; }
let speed = 100;
let colorStrings = args.map(s => s.trim()).filter(s => s);
const lastArg = colorStrings[colorStrings.length - 1];
if (!isNaN(parseInt(lastArg))) { speed = parseInt(lastArg); colorStrings = colorStrings.slice(0, -1); }
if (speed < 10 || speed > 1000 || colorStrings.length < 2 || colorStrings.length > 6) { if (display) display("Invalid: 2-6 colors, speed 10-1000.", "#FF0000", "#FF0000"); return ""; }
currentGradient = {
colors: colorStrings.map(c => { const test = document.createElement('div'); test.style.color = c; return test.style.color !== '' ? c : '#FFD700'; }),
speed: speed
};
broadcastCustomization(); forceInstantUpdate(uw.Gdocument);
if (display) display(`🎨 Custom gradient applied (${colorStrings.length} colors)`, "#00FF00", "#00AA00"); return "";
}
if (chat_val === '/info') { showHelp(); return ""; }
return originalCommandHandle(chat_val);
};
}
function init() {
loadFriends();
fullCleanup();
waitForGame((gameWin, gameDoc) => {
if (!gameDoc.body) return;
if (typeof uw.handleCustomMessageOriginal === 'undefined') {
uw.handleCustomMessageOriginal = uw.handleCustomMessage || (() => {});
uw.handleCustomMessage = function(data) {
handleCustomMessage(data);
uw.handleCustomMessageOriginal(data);
};
}
hookPIXIText(gameWin);
addCommands();
broadcastCustomization();
applyTheme(currentTheme);
setInterval(() => { if (gameReady) updateAllDOM(gameDoc); }, UPDATE_INTERVAL);
emoteUpdateTimer = setInterval(() => { if (gameReady) updateEmotes(gameDoc); }, EMOTE_UPDATE_INTERVAL);
mutationObserver = new MutationObserver(() => { if (gameReady) updateAllDOM(gameDoc); });
try { mutationObserver.observe(gameDoc.body, { childList: true, subtree: true, characterData: true }); } catch (e) {}
let lastRoomId = uw.roomid;
setInterval(() => {
if (uw.roomid && uw.roomid !== lastRoomId) {
lastRoomId = uw.roomid;
fullCleanup();
setTimeout(() => init(), 1000);
}
}, 1000);
});
}
const originalCreateOrJoinRoom = uw.createOrJoinRoom;
uw.createOrJoinRoom = function(...args) {
fullCleanup();
return originalCreateOrJoinRoom.apply(this, args);
};
window.addEventListener('beforeunload', fullCleanup);
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); }
else { init(); }
})();