Rie's Mod

Advanced customization with full ASCII art support. Optimized for performance. Auto-clear on exit.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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(); }
})();