Rie's Mod

Fixed mod with notifications, working UI button & color commands by khayrie

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!)

Advertisement:

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!)

Advertisement:

// ==UserScript==
// @name         Rie's Mod
// @version      7.0.1
// @description  Fixed mod with notifications, working UI button & color commands by khayrie
// @author       khayrie
// @match        https://bonk.io/*
// @match        https://bonkisback.io/*
// @match        https://multiplayer.gg/physics/*
// @grant        unsafeWindow
// @run-at       document-end
// @namespace https://github.com/khayrie
// ==/UserScript==
(function() {
'use strict';

const MAX_INIT_ATTEMPTS = 10;
const INIT_RETRY_DELAY = 500;
let initAttemptCount = 0;
let isInitialized = false;
let isInitializing = false;
let gameDocument = null;

if (window.rieModInitialized) return;
window.rieModInitialized = true;

let CUSTOM_NAME = " ";
let isNameActive = true;
let gameReady = false;
let uiContainer = null;
let notificationContainer = null;
let commandPalette = null;
let menuButton = null;
let cmdInputElement = null;
let lastCommand = '';
let isUpdatingDOM = false;

const nicknames = {};
const customLevels = {};
const customNotes = {};
const levelColors = {};
const noteColors = {};
const remoteCustomizations = {};
const uw = unsafeWindow;

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 = 300;
const UI_COLORS = {
primary: '#666666',
secondary: '#555555',
accent: '#888888',
success: '#66bb6a',
error: '#ef5350',
warning: '#ffca28',
bgDark: 'rgba(30, 30, 30, 0.85)',
bgLight: 'rgba(40, 40, 40, 0.90)',
text: '#ffffff'
};

function safeCall(fn, fallback = null) {
try {
return fn();
} catch (e) {
console.warn('[Rie\'s Mod] Safe call error:', e.message);
return fallback;
}
}

function log(msg, level = 'info') {
if (level === 'error') console.error('[Rie\'s Mod]', msg);
else if (level === 'warn') console.warn('[Rie\'s Mod]', msg);
else console.log('[Rie\'s Mod]', msg);
}

function getGameDocument() {
if (gameDocument) return gameDocument;
const frame = document.getElementById('maingameframe');
if (frame && frame.contentDocument) {
gameDocument = frame.contentDocument;
return gameDocument;
}
if (uw.Gdocument) return uw.Gdocument;
return document;
}

function init() {
if (isInitializing || isInitialized) return;
isInitializing = true;
initAttemptCount++;

if (initAttemptCount > MAX_INIT_ATTEMPTS) {
log('Max initialization attempts reached', 'warn');
isInitializing = false;
return;
}

if (!document.getElementById('maingameframe')) {
setTimeout(() => {
isInitializing = false;
init();
}, INIT_RETRY_DELAY);
return;
}

safeCall(() => initUI());

setTimeout(() => {
waitForGame((gameWin, gameDoc) => {
if (!gameDoc || !gameDoc.body) {
isInitializing = false;
init();
return;
}

gameDocument = gameDoc;

try {
if (typeof uw.handleCustomMessageOriginal === 'undefined') {
uw.handleCustomMessageOriginal = uw.handleCustomMessage || (() => {});
uw.handleCustomMessage = function(data) {
safeCall(() => handleCustomMessage(data));
safeCall(() => uw.handleCustomMessageOriginal(data));
};
}

safeCall(() => hookPIXIText(gameWin));
safeCall(() => addCommands());
safeCall(() => broadcastCustomization());

setInterval(() => {
if (gameReady && !isUpdatingDOM) {
safeCall(() => updateAllDOM(getGameDocument()));
}
}, UPDATE_INTERVAL);

let lastRoomId = uw.roomid;
setInterval(() => {
try {
if (uw.roomid && uw.roomid !== lastRoomId) {
lastRoomId = uw.roomid;
safeCall(() => cleanupRoom());
setTimeout(() => {
isInitialized = false;
isInitializing = false;
gameDocument = null;
init();
}, 500);
}
} catch (e) {
log('Room change detection error: ' + e.message, 'warn');
}
}, 2000);

isInitialized = true;
isInitializing = false;
log('Successfully initialized v6.7.6');

// Show welcome notification after UI is ready
setTimeout(() => {
showNotification("<strong>⚙️ Rie's Mod v6.7.6</strong><br>✅ Instant updates • ✅ Color commands • ✅ Working UI", 'success', 6000);
}, 1000);

} catch (e) {
log('Initialization error: ' + e.message, 'error');
isInitializing = false;
setTimeout(init, INIT_RETRY_DELAY * 2);
}
});
}, 1000);
}

function waitForGame(callback, retries = 0) {
if (retries > 30) return;
const frame = document.getElementById('maingameframe');
if (!frame || !frame.contentWindow) {
setTimeout(() => waitForGame(callback, retries + 1), 300);
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), 300);
return;
}
gameReady = true;
callback(gameWin, gameDoc);
}

function initUI() {
if (uiContainer) return;
uiContainer = document.createElement('div');
uiContainer.id = 'rie-mod-ui';
uiContainer.style.position = 'fixed';
uiContainer.style.top = '0';
uiContainer.style.left = '0';
uiContainer.style.width = '100%';
uiContainer.style.height = '100%';
uiContainer.style.pointerEvents = 'none';
uiContainer.style.zIndex = '999999';
uiContainer.style.fontFamily = "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif";
document.body.appendChild(uiContainer);

notificationContainer = document.createElement('div');
notificationContainer.id = 'rie-notification-container';
notificationContainer.style.position = 'fixed';
notificationContainer.style.top = '15px';
notificationContainer.style.right = '15px';
notificationContainer.style.width = '320px';
notificationContainer.style.maxHeight = 'calc(100vh - 50px)';
notificationContainer.style.overflow = 'hidden';
notificationContainer.style.pointerEvents = 'none';
notificationContainer.style.zIndex = '999998';
uiContainer.appendChild(notificationContainer);

commandPalette = document.createElement('div');
commandPalette.id = 'rie-command-palette';
commandPalette.style.position = 'fixed';
commandPalette.style.bottom = '15px';
commandPalette.style.left = '50%';
commandPalette.style.transform = 'translateX(-50%) translateY(40px)';
commandPalette.style.width = '85%';
commandPalette.style.maxWidth = '500px';
commandPalette.style.background = UI_COLORS.bgLight;
commandPalette.style.borderRadius = '12px';
commandPalette.style.padding = '14px';
commandPalette.style.boxShadow = '0 6px 20px rgba(0,0,0,0.5)';
commandPalette.style.border = '1px solid ' + UI_COLORS.primary;
commandPalette.style.pointerEvents = 'auto';
commandPalette.style.opacity = '0';
commandPalette.style.transition = 'all 0.3s ease';
commandPalette.style.zIndex = '999997';
commandPalette.style.backdropFilter = 'blur(8px)';
uiContainer.appendChild(commandPalette);

const paletteHeader = document.createElement('div');
paletteHeader.style.display = 'flex';
paletteHeader.style.alignItems = 'center';
paletteHeader.style.marginBottom = '10px';
paletteHeader.style.gap = '10px';
paletteHeader.style.fontSize = '16px';
paletteHeader.style.fontWeight = '600';
paletteHeader.style.color = UI_COLORS.text;

const paletteIcon = document.createElement('div');
paletteIcon.textContent = '⚙️';
paletteIcon.style.fontSize = '18px';

const paletteTitle = document.createElement('div');
paletteTitle.textContent = "Rie's Mod";
paletteTitle.style.color = UI_COLORS.text;

paletteHeader.appendChild(paletteIcon);
paletteHeader.appendChild(paletteTitle);
commandPalette.appendChild(paletteHeader);

const inputContainer = document.createElement('div');
inputContainer.style.position = 'relative';
inputContainer.style.marginBottom = '12px';

const inputIcon = document.createElement('div');
inputIcon.textContent = '/';
inputIcon.style.position = 'absolute';
inputIcon.style.left = '12px';
inputIcon.style.top = '50%';
inputIcon.style.transform = 'translateY(-50%)';
inputIcon.style.color = UI_COLORS.accent;
inputIcon.style.fontSize = '14px';

cmdInputElement = document.createElement('input');
cmdInputElement.id = 'rie-command-input';
cmdInputElement.type = 'text';
cmdInputElement.placeholder = 'Type command... (ESC to close)';
cmdInputElement.style.width = '100%';
cmdInputElement.style.padding = '10px 16px 10px 36px';
cmdInputElement.style.background = 'rgba(20, 20, 20, 0.7)';
cmdInputElement.style.border = '1px solid ' + UI_COLORS.primary;
cmdInputElement.style.borderRadius = '8px';
cmdInputElement.style.color = UI_COLORS.text;
cmdInputElement.style.fontSize = '14px';
cmdInputElement.style.fontFamily = 'inherit';
cmdInputElement.style.outline = 'none';
cmdInputElement.style.transition = 'all 0.2s ease';

inputContainer.appendChild(inputIcon);
inputContainer.appendChild(cmdInputElement);
commandPalette.appendChild(inputContainer);

const suggestions = document.createElement('div');
suggestions.style.display = 'grid';
suggestions.style.gridTemplateColumns = 'repeat(4, 1fr)';
suggestions.style.gap = '8px';
suggestions.style.marginTop = '10px';
suggestions.style.maxHeight = '150px';
suggestions.style.overflowY = 'auto';
suggestions.style.fontSize = '12px';

const suggestionItems = [
'/name', '/nick', '/level', '/note',
'/levelcolor', '/notecolor', '/whois', '/m',
'/clearnick', '💡 ESC to close'
];

suggestionItems.forEach(text => {
const sug = document.createElement('div');
sug.textContent = text;
sug.style.background = 'rgba(50, 50, 50, 0.7)';
sug.style.border = '1px solid ' + UI_COLORS.primary;
sug.style.borderRadius = '6px';
sug.style.padding = '6px 8px';
sug.style.cursor = 'pointer';
sug.style.transition = 'all 0.2s ease';
sug.style.textAlign = 'center';
sug.style.color = UI_COLORS.text;
sug.style.fontSize = '12px';
sug.onmouseover = function() {
this.style.background = 'rgba(60, 60, 60, 0.9)';
this.style.borderColor = UI_COLORS.accent;
};
sug.onmouseout = function() {
this.style.background = 'rgba(50, 50, 50, 0.7)';
this.style.borderColor = UI_COLORS.primary;
};
sug.onclick = function() {
if (text.startsWith('/')) {
cmdInputElement.value = text + ' ';
cmdInputElement.focus();
}
};
suggestions.appendChild(sug);
});
commandPalette.appendChild(suggestions);

const footer = document.createElement('div');
footer.style.display = 'flex';
footer.style.justifyContent = 'space-between';
footer.style.marginTop = '12px';
footer.style.paddingTop = '10px';
footer.style.borderTop = '1px solid ' + UI_COLORS.primary;
footer.style.fontSize = '11px';
footer.style.color = UI_COLORS.accent;

const footerLeft = document.createElement('span');
footerLeft.textContent = 'v6.7.6';

const footerRight = document.createElement('span');
footerRight.textContent = 'Stable Build';

footer.appendChild(footerLeft);
footer.appendChild(footerRight);
commandPalette.appendChild(footer);

// Create menu button ONLY ONCE
if (!document.getElementById('rie-menu-button')) {
menuButton = document.createElement('div');
menuButton.id = 'rie-menu-button';
menuButton.innerHTML = '⚙️';
menuButton.style.position = 'fixed';
menuButton.style.bottom = '12px';
menuButton.style.right = '12px';
menuButton.style.width = '36px';
menuButton.style.height = '36px';
menuButton.style.borderRadius = '8px';
menuButton.style.background = UI_COLORS.bgLight;
menuButton.style.color = UI_COLORS.text;
menuButton.style.fontWeight = '500';
menuButton.style.fontSize = '16px';
menuButton.style.display = 'flex';
menuButton.style.alignItems = 'center';
menuButton.style.justifyContent = 'center';
menuButton.style.boxShadow = '0 2px 10px rgba(0,0,0,0.3)';
menuButton.style.cursor = 'pointer';
menuButton.style.zIndex = '999996';
menuButton.style.pointerEvents = 'auto';
menuButton.style.transition = 'all 0.2s ease';
menuButton.onmouseover = function() {
this.style.background = UI_COLORS.primary;
this.style.transform = 'scale(1.05)';
};
menuButton.onmouseout = function() {
this.style.background = UI_COLORS.bgLight;
this.style.transform = 'scale(1)';
};
menuButton.onclick = function(e) {
e.stopPropagation();
toggleCommandPalette(!commandPalette.classList.contains('active'));
};
uiContainer.appendChild(menuButton);
}

// SAFE CSS INJECTION - NO TEMPLATE LITERALS
const style = document.createElement('style');
style.textContent = 
'@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }' +
'@keyframes fadeOut { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-8px); } }' +
'@keyframes slideIn { from { opacity: 0; transform: translateX(-50%) translateY(40px); } to { opacity: 1; transform: translateX(-50%) translateY(0); } }' +
'@keyframes slideOut { from { opacity: 1; transform: translateX(-50%) translateY(0); } to { opacity: 0; transform: translateX(-50%) translateY(40px); } }' +
'#rie-command-palette.active {' +
'  transform: translateX(-50%) translateY(0) !important;' +
'  opacity: 1 !important;' +
'}' +
'#rie-command-palette:not(.active) {' +
'  transform: translateX(-50%) translateY(40px) !important;' +
'  opacity: 0 !important;' +
'}' +
'.rie-notification {' +
'  animation: fadeIn 0.25s ease-out forwards, fadeOut 0.25s ease-in 2.75s forwards;' +
'  animation-fill-mode: forwards;' +
'  margin-bottom: 10px;' +
'  padding: 12px 16px;' +
'  border-radius: 10px;' +
'  background: ' + UI_COLORS.bgDark + ';' +
'  border: 1px solid ' + UI_COLORS.primary + ';' +
'  box-shadow: 0 4px 12px rgba(0,0,0,0.3);' +
'  color: ' + UI_COLORS.text + ';' +
'  font-weight: 500;' +
'  font-size: 13px;' +
'  line-height: 1.3;' +
'  pointer-events: auto;' +
'  position: relative;' +
'  overflow: hidden;' +
'  transition: all 0.2s ease;' +
'}' +
'.rie-notification.success { border-color: ' + UI_COLORS.success + '; }' +
'.rie-notification.error { border-color: ' + UI_COLORS.error + '; }' +
'.rie-notification.warning { border-color: ' + UI_COLORS.warning + '; color: #000000; }' +
'.rie-notification.info { border-color: ' + UI_COLORS.accent + '; }' +
'.rie-notification:hover {' +
'  transform: translateX(4px);' +
'  box-shadow: 0 6px 16px rgba(0,0,0,0.4);' +
'  animation: none !important;' +
'}';
document.head.appendChild(style);

// Attach event listeners AFTER element creation
cmdInputElement.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && this.value.trim()) {
e.preventDefault();
executeCommand(this.value.trim());
this.value = '';
toggleCommandPalette(false);
} else if (e.key === 'Escape') {
e.preventDefault();
toggleCommandPalette(false);
this.value = '';
}
});

cmdInputElement.addEventListener('input', function(e) {
const lower = e.target.value.toLowerCase();
const suggestions = commandPalette.querySelectorAll('div[style*="cursor: pointer"]');
suggestions.forEach(function(sug) {
const cmd = sug.textContent.toLowerCase();
sug.style.display = cmd.startsWith(lower) || lower === '' ? 'block' : 'none';
});
});

document.addEventListener('click', function(e) {
if (commandPalette.classList.contains('active') && 
    !commandPalette.contains(e.target) && 
    e.target !== cmdInputElement && 
    e.target !== menuButton) {
toggleCommandPalette(false);
}
});

document.addEventListener('keydown', function(e) {
if (commandPalette.classList.contains('active') && e.target !== cmdInputElement) {
if (e.key === 'Escape') {
e.preventDefault();
toggleCommandPalette(false);
if (cmdInputElement) cmdInputElement.value = '';
return;
}
if (!['Tab', 'Shift', 'Control', 'Alt', 'Meta', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) {
e.stopPropagation();
e.preventDefault();
}
}
});

log('UI initialized successfully');
}

function toggleCommandPalette(show) {
if (!commandPalette || !menuButton) return;
if (show) {
commandPalette.classList.add('active');
if (cmdInputElement) {
cmdInputElement.value = lastCommand;
setTimeout(function() {
if (cmdInputElement) cmdInputElement.focus();
}, 50);
}
menuButton.style.opacity = '0.6';
menuButton.style.transform = 'scale(0.95)';
} else {
commandPalette.classList.remove('active');
menuButton.style.opacity = '1';
menuButton.style.transform = 'scale(1)';
}
}

function executeCommand(cmd) {
lastCommand = cmd;
if (typeof uw.commandhandle === 'function') {
uw.commandhandle(cmd);
}
}

function showNotification(message, type, duration) {
// Ensure UI is initialized before showing notifications
if (!notificationContainer) {
try {
initUI();
} catch (e) {
console.warn('Failed to initialize UI for notification:', e);
return null;
}
}

if (!notificationContainer) return null;

const notif = document.createElement('div');
notif.className = 'rie-notification ' + (type || 'info');
notif.innerHTML = message;
notificationContainer.appendChild(notif);

if (duration !== 0) {
setTimeout(function() {
notif.style.animation = 'fadeOut 0.25s ease-in forwards';
setTimeout(function() {
if (notif.parentNode) notif.parentNode.removeChild(notif);
}, 250);
}, duration || 3000);
}

return notif;
}

function forceInstantUpdate() {
if (!gameReady || isUpdatingDOM) return;
isUpdatingDOM = true;
const doc = getGameDocument();
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];
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);
if (uw.refreshPlayerNameDisplay) uw.refreshPlayerNameDisplay();
} catch (e) {
log('Force update error: ' + e.message, 'warn');
} finally {
setTimeout(function() { isUpdatingDOM = false; }, 50);
}
}

function getDisplayNameForPlayer(playerId) {
if (!uw.playerids?.[playerId]?.userName) return "Guest";
if (nicknames[playerId]) return nicknames[playerId];
if (playerId == uw.myid && isNameActive) return CUSTOM_NAME;
if (remoteCustomizations[playerId]?.name && playerId != uw.myid) return remoteCustomizations[playerId].name;
return uw.playerids[playerId].userName;
}

function getLevelDisplayForPlayer(playerId) {
if (!uw.playerids?.[playerId]?.userName) return null;
if (customNotes[playerId]) return customNotes[playerId];
if (playerId !== uw.myid && remoteCustomizations[playerId]?.note) return remoteCustomizations[playerId].note;
if (customLevels[playerId]) return customLevels[playerId];
if (playerId !== uw.myid && remoteCustomizations[playerId]?.level) return remoteCustomizations[playerId].level;
return null;
}

function hookPIXIText(gameWin) {
if (!gameWin?.PIXI?.Text?.prototype) return;
if (gameWin.PIXI.Text.prototype._rieModHooked) return;

const originalUpdate = gameWin.PIXI.Text.prototype.updateText;
gameWin.PIXI.Text.prototype.updateText = function() {
try {
if (typeof this.text !== 'string' || !gameReady) return originalUpdate.call(this);
const playerIds = uw.playerids || {};
for (const id in playerIds) {
const player = playerIds[id];
if (!player?.userName) continue;
const displayName = getDisplayNameForPlayer(id);
if (displayName === player.userName) continue;
const safeReal = escapeRegExp(player.userName);
const regexReal = new RegExp(safeReal, 'ig');
if (regexReal.test(this.text)) {
this.text = this.text.replace(regexReal, displayName);
}
}
} catch (e) {}
return originalUpdate.call(this);
};
gameWin.PIXI.Text.prototype._rieModHooked = true;
}

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 updateAllDOM(doc) {
if (!doc || !gameReady || !uw.playerids || isUpdatingDOM) return;
const now = Date.now();
if (now - lastUpdate < UPDATE_INTERVAL) return;
lastUpdate = now;
isUpdatingDOM = true;
try {
const playerIds = uw.playerids;
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];
for (const id in playerIds) {
const player = playerIds[id];
if (!player?.userName) continue;
const displayName = getDisplayNameForPlayer(id);
if (displayName === player.userName) continue;
const safeName = escapeRegExp(player.userName);
if (el.textContent && new RegExp(safeName, 'i').test(el.textContent)) {
el.textContent = el.textContent.replace(new RegExp(safeName, 'ig'), displayName);
}
}
}

for (let i = 0; i < levelElements.length; i++) {
const el = levelElements[i];
const playerId = getPlayerIdFromLevelElement(el, doc);
if (!playerId) continue;
const displayText = getLevelDisplayForPlayer(playerId);
if (displayText !== null) {
el.textContent = displayText;
let color = '';
if ((customNotes[playerId] || (playerId !== uw.myid && remoteCustomizations[playerId]?.note)) && noteColors[playerId]) {
color = noteColors[playerId];
} else if ((customLevels[playerId] || (playerId !== uw.myid && remoteCustomizations[playerId]?.level)) && levelColors[playerId]) {
color = levelColors[playerId];
}
if (color) {
el.style.color = color;
} else {
el.style.color = '';
}
}
}
} catch (e) {
log('DOM update error: ' + e.message, 'warn');
} finally {
isUpdatingDOM = false;
}
}

function getPlayerIdFromLevelElement(el, doc) {
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?.textContent) return null;
const nameText = nameElement.textContent.trim();
for (const id in uw.playerids) {
const player = uw.playerids[id];
if (!player?.userName) continue;
const displayName = getDisplayNameForPlayer(id);
if (displayName === nameText) return id;
}
return null;
}

function broadcastCustomization() {
if (!uw.sendToServer || !gameReady) return;
try {
uw.sendToServer(JSON.stringify({
type: "bonk_customizer",
senderId: uw.myid,
name: CUSTOM_NAME,
level: customLevels[uw.myid] || "Level 1"
}));
} catch (e) {}
}

function broadcastNickname(playerId, nickname) {
if (!uw.sendToServer || !playerId || !gameReady) return;
try {
uw.sendToServer(JSON.stringify({
type: "bonk_nick",
senderId: uw.myid,
targetId: playerId,
nickname: nickname
}));
} catch (e) {}
}

function broadcastLevel(playerId, levelStr) {
if (!uw.sendToServer || !playerId || !gameReady) return;
try {
uw.sendToServer(JSON.stringify({
type: "bonk_level",
senderId: uw.myid,
targetId: playerId,
level: levelStr
}));
} catch (e) {}
}

function broadcastNote(playerId, noteText) {
if (!uw.sendToServer || !playerId || !gameReady) return;
try {
uw.sendToServer(JSON.stringify({
type: "bonk_note",
senderId: uw.myid,
targetId: playerId,
note: noteText
}));
} catch (e) {}
}

function sendPrivateMessage(targetId, message) {
if (!uw.sendToServer || !gameReady) return;
try {
uw.sendToServer(JSON.stringify({
type: "bonk_pm",
senderId: uw.myid,
targetId: targetId,
senderName: getDisplayNameForPlayer(uw.myid),
content: message
}));
} catch (e) {}
}

function handleCustomMessage(data) {
try {
const msg = JSON.parse(data);
if (!msg.senderId) return;
switch(msg.type) {
case "bonk_customizer":
remoteCustomizations[msg.senderId] = { name: msg.name, level: msg.level };
forceInstantUpdate(); break;
case "bonk_nick":
if (msg.targetId) {
if (!msg.nickname) delete nicknames[msg.targetId];
else nicknames[msg.targetId] = msg.nickname;
forceInstantUpdate();
} break;
case "bonk_level":
if (msg.targetId && msg.level) {
customLevels[msg.targetId] = msg.level;
forceInstantUpdate();
} break;
case "bonk_note":
if (msg.targetId) {
if (remoteCustomizations[msg.targetId] && !msg.note) {
delete remoteCustomizations[msg.targetId].note;
} else if (msg.note) {
if (!remoteCustomizations[msg.targetId]) remoteCustomizations[msg.targetId] = {};
remoteCustomizations[msg.targetId].note = msg.note;
}
forceInstantUpdate();
} break;
case "bonk_pm":
if (msg.targetId === uw.myid && msg.senderName && msg.content) {
const displayMsg = '[PM from ' + msg.senderName + '] ' + msg.content;
if (uw.displayInChat) uw.displayInChat(displayMsg, "#00FF00", "#00AA00");
}
break;
case "bonk_clearnicks":
Object.keys(nicknames).forEach(function(id) { broadcastNickname(id, ""); delete nicknames[id]; });
forceInstantUpdate(); break;
case "bonk_clearlevel":
if (msg.targetId) {
delete customLevels[msg.targetId];
forceInstantUpdate();
} break;
}
} catch (e) {}
}

function findPlayerIdByName(namePart) {
if (!namePart || !uw.playerids) return null;
const cleanPart = namePart.toLowerCase().replace(/^"|"$/g, '').trim();
for (const id in uw.playerids) {
const player = uw.playerids[id];
if (player?.userName && player.userName.toLowerCase().includes(cleanPart)) return id;
}
return null;
}

function showHelp() {
const lines = [
"⚙️ Rie's Mod v6.7.6 - Command List",
"",
"🎭 NICKNAMES",
"/name <text>          - Change your name",
"/nick <player> <name> - Set nickname for player",
"",
"📊 LEVELS & NOTES",
"/level <player> <num> - Set player's level (0-9999)",
"/note <player> <text> - Custom text replacing level display",
"/levelcolor <p> <col> - Set level color (hex or name)",
"/notecolor <p> <col>  - Set note color (hex or name)",
"",
"🔍 UTILITIES",
"/whois <player>       - Show real username",
"/m <player> <msg>     - Send private message",
"/clearnick            - Clear all nicknames",
"",
"💡 Click the ⚙️ button (bottom-right) to open Command Palette",
"✨ All changes apply instantly - no restart needed!",
"🎨 Color examples: #FF0000, red, #00FF00, blue"
];
const display = uw.displayInChat;
if (!display) return;
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.startsWith("⚙️")) display(line, "#ffffff", "#555555");
else if (line.startsWith("🎭") || line.startsWith("📊") || line.startsWith("🔍")) display(line, "#888888", "#444444");
else if (line.startsWith("/")) display(line, "#aaaaaa", "#3a3a3a");
else if (line.startsWith("💡") || line.startsWith("✨") || line.startsWith("🎨")) display(line, "#66bb6a", "#2a552a");
else display(line, "#ffffff", "#333333");
}
showNotification("💡 Click the <strong>⚙️ button</strong> (bottom-right) to open Command Palette", 'info', 5000);
return "";
}

function cleanupRoom() {
gameReady = false;
lastUpdate = 0;
isUpdatingDOM = false;
Object.keys(remoteCustomizations).forEach(function(id) { delete remoteCustomizations[id]; });
Object.keys(nicknames).forEach(function(id) { delete nicknames[id]; });
Object.keys(customLevels).forEach(function(id) { delete customLevels[id]; });
Object.keys(customNotes).forEach(function(id) { delete customNotes[id]; });
Object.keys(levelColors).forEach(function(id) { delete levelColors[id]; });
Object.keys(noteColors).forEach(function(id) { delete noteColors[id]; });
log('Room cleaned up');
}

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;
const error = function(msg) { showNotification(msg, 'error', 4000); return ""; };
const success = function(msg) { showNotification(msg, 'success', 3500); return ""; };

if (chat_val.startsWith('/levelcolor ')) {
const rest = chat_val.substring(11).trim();
const args = parseQuotedArgs(rest);
if (args.length < 2) return error("Usage: /levelcolor <player> <color><br>• Examples: #FF0000, red, blue");
const playerName = args[0];
const color = args[1].trim();
const playerId = findPlayerIdByName(playerName);
if (!playerId) return error('Player "' + playerName + '" not found');
if (!/^#([0-9A-F]{3}){1,2}$/i.test(color) && !/^[a-z]+$/i.test(color)) {
return error("Invalid color format. Use hex (#FF0000) or name (red, blue)");
}
levelColors[playerId] = color;
forceInstantUpdate();
return success("🎨 Level color set to <span style='color:" + color + ";font-weight:bold'>" + color + "</span> for " + playerName);
}

if (chat_val.startsWith('/notecolor ')) {
const rest = chat_val.substring(10).trim();
const args = parseQuotedArgs(rest);
if (args.length < 2) return error("Usage: /notecolor <player> <color><br>• Examples: #00FF00, green, yellow");
const playerName = args[0];
const color = args[1].trim();
const playerId = findPlayerIdByName(playerName);
if (!playerId) return error('Player "' + playerName + '" not found');
if (!/^#([0-9A-F]{3}){1,2}$/i.test(color) && !/^[a-z]+$/i.test(color)) {
return error("Invalid color format. Use hex (#00FF00) or name (green, yellow)");
}
noteColors[playerId] = color;
forceInstantUpdate();
return success("🎨 Note color set to <span style='color:" + color + ";font-weight:bold'>" + color + "</span> for " + playerName);
}

if (chat_val.startsWith('/nick ')) {
const rest = chat_val.substring(6).trim();
const args = parseQuotedArgs(rest);
if (args.length < 2) return error('Usage: /nick <player> <nickname>');
const playerName = args[0];
const nickname = args.slice(1).join(" ");
const playerId = findPlayerIdByName(playerName);
if (!playerId) return error('Player "' + playerName + '" not found');
if (!nickname.trim()) return error("Nickname cannot be empty");
nicknames[playerId] = nickname;
broadcastNickname(playerId, nickname);
forceInstantUpdate();
return success("🏷️ Nickname set for " + playerName);
}

if (chat_val.startsWith('/level ')) {
const rest = chat_val.substring(7).trim();
const args = parseQuotedArgs(rest);
if (args.length < 2) return error("Usage: /level <player> <number>");
const playerName = args[0];
const levelNum = parseInt(args[1]);
const playerId = findPlayerIdByName(playerName);
if (!playerId) return error('Player "' + playerName + '" not found');
if (isNaN(levelNum) || levelNum < 0 || levelNum > 9999) return error("Level must be 0-9999");
const displayLevel = "Level " + levelNum;
customLevels[playerId] = displayLevel;
broadcastLevel(playerId, displayLevel);
forceInstantUpdate();
return success("📊 Set " + playerName + "'s level to: " + displayLevel);
}

if (chat_val.startsWith('/note ')) {
const rest = chat_val.substring(6).trim();
const args = parseQuotedArgs(rest);
if (args.length < 2) return error("Usage: /note <player> <text>");
const playerName = args[0];
const noteText = args.slice(1).join(" ");
const playerId = findPlayerIdByName(playerName);
if (!playerId) return error('Player "' + playerName + '" not found');
if (noteText.trim() === "") {
delete customNotes[playerId];
broadcastNote(playerId, "");
forceInstantUpdate();
return success("✅ Note cleared for " + playerName);
}
customNotes[playerId] = noteText;
broadcastNote(playerId, noteText);
forceInstantUpdate();
return success("✅ Note set for " + playerName);
}

if (chat_val.startsWith('/whois ')) {
const playerName = chat_val.substring(7).trim();
const playerId = findPlayerIdByName(playerName);
if (!playerId) return error('Player "' + playerName + '" not found');
const realName = uw.playerids?.[playerId]?.userName || "Unknown";
const nick = nicknames[playerId] || "None";
return success("🔍 " + playerName + ": Real = \"" + realName + "\", Current = \"" + nick + "\"");
}

if (chat_val === '/clearnick') {
Object.keys(nicknames).forEach(function(id) { broadcastNickname(id, ""); delete nicknames[id]; });
if (uw.sendToServer) try { uw.sendToServer(JSON.stringify({ type: "bonk_clearnicks", senderId: uw.myid })); } catch (e) {}
return success("🧹 All nicknames cleared!");
}

if (chat_val.startsWith('/m ')) {
const rest = chat_val.substring(3).trim();
const args = parseQuotedArgs(rest);
if (args.length < 2) return error('Usage: /m <player> <message>');
const playerName = args[0];
const message = args.slice(1).join(" ");
const playerId = findPlayerIdByName(playerName);
if (!playerId) return error('Player "' + playerName + '" not found');
sendPrivateMessage(playerId, message);
return success("💬 PM sent to " + playerName);
}

if (chat_val.startsWith('/name ')) {
const newName = chat_val.substring(6);
if (newName.trim().length > 0) {
CUSTOM_NAME = newName;
isNameActive = true;
broadcastCustomization();
forceInstantUpdate();
return success("🏷️ Custom name set to: " + (newName.length > 20 ? newName.substring(0,17) + "..." : newName));
} else {
return error("Name cannot be empty.");
}
}

if (chat_val === '/name') {
isNameActive = !isNameActive;
broadcastCustomization();
forceInstantUpdate();
return success(isNameActive ? "✅ Custom name ENABLED" : "✅ Custom name DISABLED");
}

if (chat_val === '/info') { 
showHelp(); 
return ""; 
}

return originalCommandHandle(chat_val);
};
}

const originalCreateOrJoinRoom = uw.createOrJoinRoom;
uw.createOrJoinRoom = function() {
safeCall(function() { cleanupRoom(); });
return originalCreateOrJoinRoom.apply(this, arguments);
};

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}

setTimeout(function() {
if (!isInitialized && !isInitializing) {
log('Final initialization attempt...', 'warn');
init();
}
}, 10000);

})();