Fixed mod with notifications, working UI button & color commands by khayrie
// ==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);
})();