Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==================================
- // Draggable HUDs Component (User Tracker & Separate Theme Editor & Options)
- // FIXED: Theme Editor now manages saving and loading custom themes
- // ADDED: Maximum of 10 custom themes enforced and user notified.
- // ADDED: Separate Options HUD component.
- // ==================================
- let followMode = false;
- let followIndex = -1;
- let utid = -1;
- // Global references for the HUD elements
- let hudElement = null;
- let themeEditorElement = null;
- let optionsHudElement = null; // NEW: Options HUD reference
- // Global reference for the TEMPORARY theme being edited
- let editTheme = null;
- // Tracks the key of the custom theme currently loaded into the editor
- let editingCustomKey = null;
- // The maximum number of custom themes allowed
- const MAX_CUSTOM_THEMES = 10;
- // ==================================
- // THEME DEFINITIONS (Built-in themes are kept separate)
- // ==================================
- const BUILT_IN_THEMES = {
- dark: {
- name: "Dark (Default)",
- boxBg: "#222",
- boxText: "#fff",
- border: "#444",
- headerBg: "#111",
- selectBg: "#333",
- selectText: "#fff",
- toggleColor: "#fff",
- custom: false
- },
- light: {
- name: "Light (Default)",
- boxBg: "#fafafa",
- boxText: "#000",
- border: "#ccc",
- headerBg: "#e6e6e6",
- selectBg: "#fff",
- selectText: "#000",
- toggleColor: "#000",
- custom: false
- },
- win95: {
- name: "Windows 95 (Default)",
- boxBg: "#C0C0C0",
- boxText: "#000000",
- border: "#808080",
- headerBg: "#000080",
- selectBg: "#FFFFFF",
- selectText: "#000000",
- toggleColor: "#FFFFFF",
- custom: false
- },
- };
- // Merged themes: BUILT_IN_THEMES + Custom Themes from localStorage
- let HUD_THEMES = JSON.parse(JSON.stringify(BUILT_IN_THEMES));
- let currentTheme = "dark";
- // ----------------------------------------------------
- // LocalStorage Functions
- // ----------------------------------------------------
- function loadCustomThemes() {
- try {
- const storedThemes = localStorage.getItem('hudCustomThemes');
- if (storedThemes) {
- const customThemes = JSON.parse(storedThemes);
- Object.assign(HUD_THEMES, customThemes);
- }
- } catch (e) {
- console.error("Error loading custom themes from localStorage:", e);
- HUD_THEMES = JSON.parse(JSON.stringify(BUILT_IN_THEMES));
- }
- }
- function saveCustomThemes() {
- try {
- const customThemesToSave = {};
- // 1. Identify all custom themes
- for (const key in HUD_THEMES) {
- if (HUD_THEMES[key].custom) {
- customThemesToSave[key] = HUD_THEMES[key];
- }
- }
- // 2. Save the custom themes (no FIFO deletion needed anymore,
- // as the save button logic now prevents exceeding the limit).
- localStorage.setItem('hudCustomThemes', JSON.stringify(customThemesToSave));
- console.log(`Custom themes saved. Total active custom themes: ${Object.keys(customThemesToSave).length}`);
- } catch (e) {
- console.error("Error saving custom themes to localStorage:", e);
- }
- }
- // ----------------------------------------------------
- // Utility Functions
- // ----------------------------------------------------
- // Utility: safe create element with props
- function el(name, props = {}) {
- const e = document.createElement(name);
- Object.keys(props).forEach(k => {
- if (k === "style") {
- Object.assign(e.style, props.style);
- } else if (k === "html") {
- e.innerHTML = props.html;
- } else if (k === "text") {
- e.textContent = props.text;
- } else {
- e.setAttribute(k, props[k]);
- }
- });
- return e;
- }
- // Utility: Rebuilds the main HUD theme selection dropdown
- function populateThemeSelect(selectEl, selectedKey) {
- selectEl.innerHTML = '';
- // Add Built-in themes
- const builtInGroup = el('optgroup', { label: 'Built-in Themes' });
- for (const key in BUILT_IN_THEMES) {
- const opt = el("option", { value: key, text: BUILT_IN_THEMES[key].name });
- builtInGroup.appendChild(opt);
- }
- selectEl.appendChild(builtInGroup);
- // Add Custom themes
- const customThemeKeys = Object.keys(HUD_THEMES).filter(key => HUD_THEMES[key].custom);
- if (customThemeKeys.length > 0) {
- const customGroup = el('optgroup', { label: 'Custom Themes' });
- customThemeKeys.forEach(key => {
- const opt = el("option", { value: key, text: HUD_THEMES[key].name });
- customGroup.appendChild(opt);
- });
- selectEl.appendChild(customGroup);
- }
- selectEl.value = selectedKey || 'dark';
- }
- // Utility: Rebuilds the Editor's custom theme selection dropdown
- function populateEditorThemeSelect(selectEl, selectedKey) {
- selectEl.innerHTML = '';
- // Always include the "New Theme" option
- const newOpt = el("option", { value: 'NEW', text: '-- Create New Theme --' });
- selectEl.appendChild(newOpt);
- // Add Custom themes
- const customThemeKeys = Object.keys(HUD_THEMES).filter(key => HUD_THEMES[key].custom);
- if (customThemeKeys.length > 0) {
- // Sort keys by name for a cleaner list
- customThemeKeys.sort((a, b) => HUD_THEMES[a].name.localeCompare(HUD_THEMES[b].name));
- customThemeKeys.forEach(key => {
- const opt = el("option", { value: key, text: HUD_THEMES[key].name.replace(' (Custom)', '') });
- selectEl.appendChild(opt);
- });
- }
- selectEl.value = selectedKey || 'NEW';
- }
- // ----------------------------------------------------
- // Custom Theme Application Function for all HUDS
- // ----------------------------------------------------
- function applyHUDTheme(themeKeyOrObject) {
- const t = editTheme
- ? editTheme
- : (typeof themeKeyOrObject === "object" && themeKeyOrObject !== null
- ? themeKeyOrObject
- : HUD_THEMES[currentTheme] || HUD_THEMES.dark);
- // Apply to Main HUD
- const hud = document.getElementById('draggable-hud');
- if (hud) {
- const header = document.getElementById('hud-header');
- const content = document.getElementById('hud-content');
- const minBtn = document.getElementById('hud-min-btn');
- const select = document.getElementById('userJumpSelect');
- const themeSel = document.getElementById('themeSelect');
- const editorBtn = document.getElementById('openEditorBtn');
- const optionsBtn = document.getElementById('openOptionsBtn'); // NEW
- hud.style.backgroundColor = t.boxBg;
- hud.style.color = t.boxText;
- hud.style.border = `1px solid ${t.border}`;
- if (header) {
- header.style.backgroundColor = t.headerBg;
- header.style.color = t.boxText;
- }
- if (content) content.style.color = t.boxText;
- if (minBtn) minBtn.style.color = t.toggleColor;
- if (select) {
- select.style.background = t.selectBg || t.boxBg;
- select.style.color = t.selectText || t.boxText;
- select.style.border = `1px solid ${t.border}`;
- }
- if (themeSel) {
- themeSel.style.background = t.selectBg || t.boxBg;
- themeSel.style.color = t.selectText || t.boxText;
- themeSel.style.border = `1px solid ${t.border}`;
- if (!editTheme && typeof themeKeyOrObject !== "object") {
- themeSel.value = currentTheme;
- }
- }
- if (editorBtn) {
- editorBtn.style.background = t.selectBg || t.boxBg;
- editorBtn.style.color = t.selectText || t.boxText;
- editorBtn.style.border = `1px solid ${t.border}`;
- }
- if (optionsBtn) { // NEW
- optionsBtn.style.background = t.selectBg || t.boxBg;
- optionsBtn.style.color = t.selectText || t.boxText;
- optionsBtn.style.border = `1px solid ${t.border}`;
- }
- }
- // Apply to Editor HUD if it exists
- const editorHud = document.getElementById('theme-editor-hud');
- if (editorHud) {
- const editorHeader = document.getElementById('editor-hud-header');
- const deleteBtn = document.getElementById('deleteCustomThemeBtn');
- editorHud.style.backgroundColor = t.boxBg;
- editorHud.style.color = t.boxText;
- editorHud.style.border = `1px solid ${t.border}`;
- if (editorHeader) {
- editorHeader.style.backgroundColor = t.headerBg;
- editorHeader.style.color = t.boxText;
- editorHeader.style.borderBottom = `1px solid ${t.border}`;
- }
- // Update delete button visibility based on the theme loaded into the editor
- if (deleteBtn) {
- deleteBtn.style.display = editingCustomKey ? 'inline-block' : 'none';
- }
- }
- // NEW: Apply to Options HUD if it exists
- const optionsHud = document.getElementById('options-hud');
- if (optionsHud) {
- const optionsHeader = document.getElementById('options-hud-header');
- const optionsMinBtn = document.getElementById('options-min-btn');
- optionsHud.style.backgroundColor = t.boxBg;
- optionsHud.style.color = t.boxText;
- optionsHud.style.border = `1px solid ${t.border}`;
- if (optionsHeader) {
- optionsHeader.style.backgroundColor = t.headerBg;
- optionsHeader.style.color = t.boxText;
- optionsHeader.style.borderBottom = `1px solid ${t.border}`;
- }
- if (optionsMinBtn) optionsMinBtn.style.color = t.toggleColor;
- }
- // Update editor inputs if theme changes from dropdown (not from editor)
- if (!editTheme) {
- updateThemeEditorInputs(t);
- }
- }
- // Helper to sync editor inputs with the current active theme
- function updateThemeEditorInputs(theme) {
- const editorEl = document.getElementById('theme-editor-hud');
- if (!editorEl) return;
- // Update theme name input
- const themeNameInput = document.getElementById('edit_name');
- if (themeNameInput) {
- // Remove ' (Custom)' or ' (Default)' before showing in the editor input
- themeNameInput.value = theme.name.replace(' (Custom)', '').replace(' (Default)', '');
- }
- // Update color inputs
- document.getElementById('edit_boxBg').value = theme.boxBg || '#000000';
- document.getElementById('edit_boxText').value = theme.boxText || '#FFFFFF';
- document.getElementById('edit_border').value = theme.border || '#999999';
- document.getElementById('edit_headerBg').value = theme.headerBg || '#000000';
- document.getElementById('edit_selectBg').value = theme.selectBg || theme.boxBg || '#000000';
- document.getElementById('edit_selectText').value = theme.selectText || theme.boxText || '#FFFFFF';
- document.getElementById('edit_toggleColor').value = theme.toggleColor || theme.boxText || '#FFFFFF';
- }
- /**
- * Loads a theme into the editor.
- * @param {string} key - The key of the theme to load (built-in or custom).
- */
- function loadThemeIntoEditor(key) {
- const themeToLoad = HUD_THEMES[key] || HUD_THEMES.dark;
- // 1. Set the editor state variables
- editTheme = JSON.parse(JSON.stringify(themeToLoad)); // Create a copy for live editing
- editingCustomKey = themeToLoad.custom ? key : null;
- // 2. Update editor inputs and apply theme preview
- updateThemeEditorInputs(editTheme);
- applyHUDTheme(editTheme); // Apply the editTheme preview
- // 3. Update the button states
- const saveButton = document.getElementById('saveThemeBtn');
- if (saveButton) {
- saveButton.textContent = editingCustomKey ? "Update Theme" : "Save As New";
- }
- }
- // ----------------------------------------------------
- // Theme Editor HUD Creation
- // ----------------------------------------------------
- function createThemeEditorHUD() {
- if (themeEditorElement) {
- return themeEditorElement;
- }
- // Initialize the editor with the currently applied theme
- loadThemeIntoEditor(currentTheme);
- // --- HUD Element Setup ---
- const hud = document.createElement('div');
- hud.id = 'theme-editor-hud';
- Object.assign(hud.style, {
- position: 'absolute',
- backgroundColor: HUD_THEMES[currentTheme].boxBg,
- border: `1px solid ${HUD_THEMES[currentTheme].border}`,
- borderRadius: '8px',
- width: '320px', // Slightly wider for new controls
- zIndex: '1001',
- top: '100px',
- left: '420px',
- boxShadow: '2px 2px 5px rgba(0,0,0,0.2)',
- fontFamily: 'monospace',
- fontSize: '11px',
- overflow: 'hidden',
- });
- // --- Header Setup ---
- const header = document.createElement('div');
- header.id = 'editor-hud-header';
- Object.assign(header.style, {
- backgroundColor: HUD_THEMES[currentTheme].headerBg,
- height: '25px',
- cursor: 'grab',
- display: 'flex',
- borderRadius: '8px 8px 0px 0px',
- justifyContent: 'space-between',
- alignItems: 'center',
- color: HUD_THEMES[currentTheme].boxText,
- paddingLeft: '5px',
- userSelect: 'none'
- });
- header.textContent = 'Theme Editor';
- // --- Close Button ---
- const closeButton = document.createElement('span');
- closeButton.textContent = 'X';
- Object.assign(closeButton.style, {
- cursor: 'pointer',
- padding: '0 5px',
- fontWeight: 'bold',
- color: HUD_THEMES[currentTheme].toggleColor,
- fontSize: '14px'
- });
- closeButton.addEventListener('click', () => {
- hud.remove();
- themeEditorElement = null;
- editTheme = null;
- editingCustomKey = null;
- applyHUDTheme(); // Revert preview to the actual currentTheme
- });
- header.appendChild(closeButton);
- hud.appendChild(header);
- // --- Content Area (Editor Controls) ---
- const content = document.createElement('div');
- content.id = 'editor-hud-content';
- Object.assign(content.style, {
- padding: '10px',
- display: 'flex',
- flexDirection: 'column'
- });
- // Custom Theme Selection Dropdown
- const themeSelectWrap = el("div", {
- style: { marginBottom: "8px", borderBottom: `1px solid ${HUD_THEMES.dark.border}`, paddingBottom: '8px' }
- });
- const selectLabel = el("label", { text: "Load Custom Theme:", style: { fontSize: "10px", display: "block", marginBottom: "2px" } });
- const editorThemeSel = el("select", {
- id: "editorThemeSelect",
- style: { width: "100%", padding: "2px", border: "1px solid #888" }
- });
- populateEditorThemeSelect(editorThemeSel, editingCustomKey || 'NEW');
- editorThemeSel.addEventListener("change", (e) => {
- const key = e.target.value;
- if (key === 'NEW') {
- // Load the current main theme for starting a new custom theme
- loadThemeIntoEditor(currentTheme);
- } else {
- // Load the selected custom theme for editing
- loadThemeIntoEditor(key);
- }
- });
- themeSelectWrap.appendChild(selectLabel);
- themeSelectWrap.appendChild(editorThemeSel);
- content.appendChild(themeSelectWrap);
- // Theme Name Input
- const nameWrap = el("div", { style: { marginBottom: "8px", display: "flex", alignItems: "center" } });
- const nameLabel = el("label", { text: "Theme Name:", style: { fontSize: "10px", marginRight: "8px" } });
- const nameInput = el("input", {
- type: 'text',
- id: 'edit_name',
- // Value is set by loadThemeIntoEditor
- style: { width: "100%", padding: "2px", border: "1px solid #888" }
- });
- nameWrap.appendChild(nameLabel);
- nameWrap.appendChild(nameInput);
- content.appendChild(nameWrap);
- // Theme Keys and Grid Setup
- const themeKeys = Object.keys(BUILT_IN_THEMES.dark).filter(k => k !== 'name' && k !== 'custom');
- const grid = el("div", {
- style: {
- display: "grid",
- gridTemplateColumns: "1fr 1fr",
- gap: "4px 8px"
- }
- });
- themeKeys.forEach(key => {
- const labelText = key.replace(/([A-Z])/g, ' $1').replace(/^box/, 'HUD').replace(/^header/, 'Header').replace(/^select/, 'Select').trim();
- const label = el("label", {
- text: labelText + ":",
- style: { display: "block", fontSize: "10px", textAlign: "right" }
- });
- const input = el("input", {
- type: 'color',
- id: `edit_${key}`,
- // Value is set by loadThemeIntoEditor
- style: { width: "100%", padding: "1px", border: "1px solid #888" }
- });
- input.addEventListener('input', (e) => {
- // Update the live preview theme (editTheme)
- if (!editTheme) {
- editTheme = JSON.parse(JSON.stringify(HUD_THEMES[currentTheme]));
- }
- editTheme[key] = e.target.value;
- applyHUDTheme(editTheme);
- });
- grid.appendChild(label);
- grid.appendChild(input);
- });
- content.appendChild(grid);
- // Reset and Apply/Save Buttons
- const actionsBottom = el("div", {
- style: {
- display: "flex",
- justifyContent: "space-between",
- marginTop: "10px",
- paddingTop: "6px",
- borderTop: `1px solid ${HUD_THEMES.dark.border}`,
- }
- });
- // Reset to initial editor load state
- const resetButton = el("button", {
- text: "Reset Edits",
- style: { padding: "4px 8px", cursor: "pointer", flexGrow: 1, marginRight: "4px", fontSize: '10px' }
- });
- resetButton.addEventListener('click', () => {
- // Reloads the theme that was originally opened in the editor
- loadThemeIntoEditor(editingCustomKey || currentTheme);
- });
- // Save/Update Button
- const saveButton = el("button", {
- id: "saveThemeBtn",
- text: editingCustomKey ? "Update Theme" : "Save As New",
- style: {
- padding: "4px 8px",
- cursor: "pointer",
- flexGrow: 1,
- marginLeft: "4px",
- fontSize: '10px',
- fontWeight: 'bold'
- }
- });
- saveButton.addEventListener('click', () => {
- // Use the temporary live editTheme for saving
- if (!editTheme) return;
- const customThemeKeys = Object.keys(HUD_THEMES).filter(key => HUD_THEMES[key].custom);
- if (!editingCustomKey && customThemeKeys.length >= MAX_CUSTOM_THEMES) {
- alert(`🛑 Cannot save new theme. You have reached the maximum limit of ${MAX_CUSTOM_THEMES} custom themes. Please delete an existing custom theme first.`);
- return; // STOP save operation
- }
- const newName = document.getElementById('edit_name').value.trim() || 'Custom Theme';
- let keyToSave;
- if (editingCustomKey) {
- // --- UPDATE EXISTING THEME ---
- keyToSave = editingCustomKey;
- } else {
- // --- SAVE AS NEW THEME ---
- keyToSave = 'custom_' + Date.now();
- }
- // Apply final changes and mark as custom
- editTheme.name = newName + ' (Custom)';
- editTheme.custom = true;
- // Save to global list
- HUD_THEMES[keyToSave] = editTheme;
- // 1. Save to storage
- saveCustomThemes();
- // 2. Set the saved theme as the current theme
- currentTheme = keyToSave;
- // 3. Update all dropdowns (main HUD and editor HUD)
- const themeSelMain = document.getElementById('themeSelect');
- if(themeSelMain) populateThemeSelect(themeSelMain, currentTheme);
- populateEditorThemeSelect(document.getElementById('editorThemeSelect'), keyToSave);
- // 4. Reset editor state to reflect the saved theme
- loadThemeIntoEditor(keyToSave);
- alert(`Theme saved successfully as: ${newName}!`);
- });
- actionsBottom.appendChild(resetButton);
- actionsBottom.appendChild(saveButton);
- // Delete Button
- const deleteButton = el("button", {
- id: "deleteCustomThemeBtn",
- text: "Delete Theme",
- style: {
- padding: "4px 8px",
- cursor: "pointer",
- flexGrow: 1,
- marginTop: "4px",
- fontSize: '10px',
- backgroundColor: '#ff9999',
- // Display is toggled in applyHUDTheme based on editingCustomKey
- }
- });
- deleteButton.addEventListener('click', () => {
- if (!editingCustomKey) return;
- if (confirm(`Are you sure you want to delete the theme "${HUD_THEMES[editingCustomKey].name}"?`)) {
- // Delete from the merged object
- delete HUD_THEMES[editingCustomKey];
- // 1. Save state
- saveCustomThemes();
- // 2. Revert to a default theme if the deleted one was current
- if (currentTheme === editingCustomKey) {
- currentTheme = 'dark';
- }
- // 3. Update all dropdowns
- const themeSelMain = document.getElementById('themeSelect');
- if(themeSelMain) populateThemeSelect(themeSelMain, currentTheme);
- // 4. Reset the editor to "New Theme" state
- loadThemeIntoEditor(currentTheme);
- populateEditorThemeSelect(document.getElementById('editorThemeSelect'), 'NEW');
- }
- });
- content.appendChild(actionsBottom);
- content.appendChild(deleteButton); // Add the delete button below the main actions
- hud.appendChild(content);
- document.body.appendChild(hud);
- themeEditorElement = hud;
- // Final sync after all elements are attached
- applyHUDTheme();
- // --- Drag Logic --- (Standard drag implementation)
- let isDragging = false;
- let offsetX, offsetY;
- header.addEventListener('mousedown', (e) => {
- if (e.target === closeButton) return;
- isDragging = true;
- offsetX = e.clientX - hud.getBoundingClientRect().left;
- offsetY = e.clientY - hud.getBoundingClientRect().top;
- header.style.cursor = 'grabbing';
- document.body.style.userSelect = 'none';
- });
- document.addEventListener('mousemove', (e) => {
- if (!isDragging) return;
- hud.style.left = (e.clientX - offsetX) + 'px';
- hud.style.top = (e.clientY - offsetY) + 'px';
- });
- document.addEventListener('mouseup', () => {
- isDragging = false;
- header.style.cursor = 'grab';
- document.body.style.userSelect = '';
- });
- return hud;
- }
- // ----------------------------------------------------
- // Options HUD Component Creation (NEW FUNCTION)
- // ----------------------------------------------------
- function createOptionsHUD() {
- if (optionsHudElement) {
- return optionsHudElement;
- }
- // --- HUD Element Setup ---
- const hud = document.createElement('div');
- hud.id = 'options-hud';
- const initialTheme = HUD_THEMES[currentTheme];
- Object.assign(hud.style, {
- position: 'absolute',
- backgroundColor: initialTheme.boxBg,
- border: `1px solid ${initialTheme.border}`,
- borderRadius: '8px',
- width: '250px', // Smaller size
- height: '150px',
- zIndex: '1002', // Higher Z-index than main HUD (1000)
- top: '150px',
- left: '50px',
- boxShadow: '2px 2px 5px rgba(0,0,0,0.2)',
- fontFamily: 'monospace',
- fontSize: '11px',
- overflow: 'hidden',
- transition: 'height 0.2s ease',
- color: initialTheme.boxText
- });
- // --- Header Setup ---
- const header = document.createElement('div');
- header.id = 'options-hud-header';
- Object.assign(header.style, {
- backgroundColor: initialTheme.headerBg,
- height: '25px',
- cursor: 'grab',
- display: 'flex',
- borderRadius: '8px 8px 0px 0px',
- justifyContent: 'space-between',
- alignItems: 'center',
- color: initialTheme.boxText,
- paddingLeft: '5px',
- userSelect: 'none'
- });
- header.textContent = 'Options Menu';
- // --- Button Container ---
- const buttonContainer = document.createElement('div');
- buttonContainer.style.display = 'flex';
- // --- Minimize Button ---
- const minimizeButton = document.createElement('span');
- minimizeButton.id = 'options-min-btn';
- minimizeButton.textContent = '-';
- Object.assign(minimizeButton.style, {
- cursor: 'pointer',
- padding: '0 5px',
- fontWeight: 'bold',
- color: initialTheme.toggleColor,
- fontSize: '18px'
- });
- buttonContainer.appendChild(minimizeButton);
- // --- Close Button ---
- const closeButton = document.createElement('span');
- closeButton.textContent = 'X';
- Object.assign(closeButton.style, {
- cursor: 'pointer',
- padding: '0 5px',
- fontWeight: 'bold',
- color: initialTheme.toggleColor,
- fontSize: '14px'
- });
- closeButton.addEventListener('click', () => {
- hud.remove();
- optionsHudElement = null;
- });
- buttonContainer.appendChild(closeButton);
- header.appendChild(buttonContainer);
- hud.appendChild(header);
- // --- Content Area (Currently Empty) ---
- const content = document.createElement('div');
- content.id = 'options-hud-content';
- Object.assign(content.style, {
- padding: '10px',
- height: 'calc(100% - 25px)',
- overflow: 'auto',
- display: 'flex',
- flexDirection: 'column'
- });
- content.textContent = "Options content goes here..."; // Placeholder text
- hud.appendChild(content);
- document.body.appendChild(hud);
- optionsHudElement = hud;
- // --- Drag & Minimize Logic ---
- let isDragging = false;
- let offsetX, offsetY;
- const originalHeight = '150px';
- const headerHeight = '25px';
- header.addEventListener('mousedown', (e) => {
- if (e.target.tagName !== 'SPAN') { // Don't drag when clicking buttons
- isDragging = true;
- offsetX = e.clientX - hud.getBoundingClientRect().left;
- offsetY = e.clientY - hud.getBoundingClientRect().top;
- header.style.cursor = 'grabbing';
- document.body.style.userSelect = 'none';
- }
- });
- document.addEventListener('mousemove', (e) => {
- if (!isDragging) return;
- hud.style.left = (e.clientX - offsetX) + 'px';
- hud.style.top = (e.clientY - offsetY) + 'px';
- });
- document.addEventListener('mouseup', () => {
- isDragging = false;
- header.style.cursor = 'grab';
- document.body.style.userSelect = '';
- });
- minimizeButton.addEventListener('click', () => {
- if (content.style.display === 'none') {
- content.style.display = 'flex';
- hud.style.height = originalHeight;
- minimizeButton.textContent = '-';
- } else {
- content.style.display = 'none';
- hud.style.height = headerHeight;
- minimizeButton.textContent = '+';
- }
- });
- return hud;
- }
- // ----------------------------------------------------
- // Main HUD Component Creation (User Tracker)
- // ----------------------------------------------------
- function createDraggableHUD(usernamesRaw) {
- if (hudElement) {
- return hudElement;
- }
- // --- HUD Element Setup ---
- const hud = document.createElement('div');
- hud.id = 'draggable-hud';
- Object.assign(hud.style, {
- position: 'absolute',
- backgroundColor: 'lightgray',
- border: '1px solid black',
- borderRadius: '8px',
- width: '350px',
- height: '300px',
- zIndex: '1000',
- top: '50px',
- left: '50px',
- boxShadow: '2px 2px 5px rgba(0,0,0,0.2)',
- fontFamily: 'monospace',
- fontSize: '11px',
- overflow: 'hidden',
- transition: 'height 0.2s ease'
- });
- // --- Header Setup ---
- const header = document.createElement('div');
- header.id = 'hud-header';
- Object.assign(header.style, {
- backgroundColor: 'gray',
- height: '25px',
- cursor: 'grab',
- display: 'flex',
- borderRadius: '8px 8px 0px 0px',
- justifyContent: 'space-between',
- alignItems: 'center',
- color: 'white',
- paddingLeft: '5px',
- userSelect: 'none'
- });
- header.textContent = 'User Tracker HUD';
- // --- Button Container ---
- const buttonContainer = document.createElement('div');
- buttonContainer.style.display = 'flex';
- // --- Minimize Button ---
- const minimizeButton = document.createElement('span');
- minimizeButton.id = 'hud-min-btn';
- minimizeButton.textContent = '-';
- Object.assign(minimizeButton.style, {
- cursor: 'pointer',
- padding: '0 5px',
- fontWeight: 'bold',
- color: 'white',
- fontSize: '18px'
- });
- buttonContainer.appendChild(minimizeButton);
- // --- Close Button ---
- const closeButton = document.createElement('span');
- closeButton.textContent = 'X';
- Object.assign(closeButton.style, {
- cursor: 'pointer',
- padding: '0 5px',
- fontWeight: 'bold',
- color: 'white',
- fontSize: '14px'
- });
- closeButton.addEventListener('click', () => {
- hud.remove();
- hudElement = null;
- if (themeEditorElement) {
- themeEditorElement.remove();
- themeEditorElement = null;
- editingCustomKey = null;
- editTheme = null;
- }
- if (optionsHudElement) { // NEW: Close Options HUD as well
- optionsHudElement.remove();
- optionsHudElement = null;
- }
- });
- buttonContainer.appendChild(closeButton);
- header.appendChild(buttonContainer);
- hud.appendChild(header);
- // --- Content Area ---
- const content = document.createElement('div');
- content.id = 'hud-content';
- Object.assign(content.style, {
- padding: '5px 10px',
- height: 'calc(100% - 25px)',
- overflow: 'hidden',
- display: 'flex',
- flexDirection: 'column'
- });
- // Controls container
- const controls = el("div", {
- style: { marginBottom: "8px", borderBottom: '1px dashed #999', paddingBottom: '5px', flexShrink: 0 }
- });
- // Select (Teleport / Follow)
- const select = el("select", {
- id: "userJumpSelect",
- style: { width: "100%", margin: "4px 0", padding: "2px" }
- });
- select.addEventListener("change", () => {
- const idx = parseInt(select.value, 10);
- if (!isNaN(idx)) {
- utid = idx;
- if (followMode) followIndex = idx;
- }
- });
- // Follow checkbox
- const followWrap = el("label", {
- style: { display: "flex", alignItems: "center", gap: "6px", margin: "6px 0" }
- });
- const followCB = el("input", { type: "checkbox", id: "followCheck" });
- followCB.checked = followMode;
- followCB.addEventListener("change", () => {
- followMode = followCB.checked;
- if (followMode) {
- const idx = parseInt(select.value, 10);
- if (!isNaN(idx)) followIndex = idx;
- } else {
- followIndex = -1;
- }
- });
- const followLabel = el("span", { text: "Follow user" });
- followWrap.appendChild(followCB);
- followWrap.appendChild(followLabel);
- // Theme selector dropdown (Main HUD)
- const themeSel = el("select", {
- id: "themeSelect",
- style: { width: "calc(35% - 4px)", margin: "4px 0", padding: "2px", display: 'inline-block', verticalAlign: 'top', marginRight: '4px' }
- });
- // Populate the main theme select
- populateThemeSelect(themeSel, currentTheme);
- themeSel.addEventListener("change", () => {
- currentTheme = themeSel.value;
- editTheme = null;
- applyHUDTheme();
- });
- // Theme Editor Button
- const editorButton = el("button", {
- id: "openEditorBtn",
- text: "Edit Theme",
- style: {
- width: "35%",
- padding: "2px",
- margin: "4px 0",
- cursor: "pointer",
- display: 'inline-block',
- verticalAlign: 'top'
- }
- });
- editorButton.addEventListener('click', () => {
- if (!themeEditorElement) {
- createThemeEditorHUD();
- } else {
- themeEditorElement.remove();
- themeEditorElement = null;
- editingCustomKey = null;
- editTheme = null;
- applyHUDTheme();
- }
- });
- // NEW: Options Button
- const optionsButton = el("button", {
- id: "openOptionsBtn",
- text: "Options",
- style: {
- width: "30%",
- padding: "2px",
- margin: "4px 0",
- cursor: "pointer",
- display: 'inline-block',
- verticalAlign: 'top',
- marginLeft: '4px' // Add spacing
- }
- });
- optionsButton.addEventListener('click', () => {
- if (!optionsHudElement) {
- createOptionsHUD();
- } else {
- optionsHudElement.remove();
- optionsHudElement = null;
- }
- });
- controls.appendChild(select);
- controls.appendChild(followWrap);
- const themeControls = el("div", {
- style: { display: 'flex', justifyContent: 'space-between'}
- });
- themeControls.appendChild(themeSel);
- themeControls.appendChild(editorButton);
- themeControls.appendChild(optionsButton); // APPEND THE NEW BUTTON
- controls.appendChild(themeControls);
- content.appendChild(controls);
- // User List Area
- const userList = el("div", {
- id: 'userListContent',
- style: {
- whiteSpace: 'pre',
- overflowX: 'auto',
- overflowY: 'auto',
- flexGrow: 1,
- marginTop: '5px'
- }
- });
- content.appendChild(userList);
- hud.appendChild(content);
- document.body.appendChild(hud);
- hudElement = hud;
- // --- Drag Logic Variables/Listeners (omitted for brevity) ---
- let isDragging = false;
- let offsetX, offsetY;
- const originalHeight = '300px';
- const headerHeight = '25px';
- header.addEventListener('mousedown', (e) => {
- if (e.target === closeButton || e.target === minimizeButton) return;
- isDragging = true;
- offsetX = e.clientX - hud.getBoundingClientRect().left;
- offsetY = e.clientY - hud.getBoundingClientRect().top;
- header.style.cursor = 'grabbing';
- document.body.style.userSelect = 'none';
- });
- document.addEventListener('mousemove', (e) => {
- if (!isDragging) return;
- hud.style.left = (e.clientX - offsetX) + 'px';
- hud.style.top = (e.clientY - offsetY) + 'px';
- });
- document.addEventListener('mouseup', () => {
- isDragging = false;
- header.style.cursor = 'grab';
- document.body.style.userSelect = '';
- });
- minimizeButton.addEventListener('click', () => {
- if (content.style.display === 'none') {
- content.style.display = 'flex';
- hud.style.height = originalHeight;
- minimizeButton.textContent = '-';
- } else {
- content.style.display = 'none';
- hud.style.height = headerHeight;
- minimizeButton.textContent = '+';
- }
- });
- // Initial theme application
- applyHUDTheme();
- return hud;
- }
- // ----------------------------------------------------
- // Main Loop Update Function (ut)
- // ----------------------------------------------------
- function ut() {
- try {
- if (typeof w === "undefined" || !w || !w.cursors) {
- if (!hudElement) createDraggableHUD([]);
- const userList = document.getElementById('userListContent');
- if (userList) userList.textContent = "Waiting for 'w' (game object) to be available.";
- requestAnimationFrame(ut);
- return;
- }
- const a = Array.from(w.cursors);
- let out = "";
- const usernamesRaw = [];
- for (let i = 0; i < a.length; i++) {
- const cur = a[i][1];
- const nameHTML = `<span style="color:${cur.c}">${cur.n}</span>`;
- usernamesRaw.push(cur.n);
- const coords = cur.l && cur.l.length === 2 ? `(${cur.l[0]}, ${cur.l[1]})` : 'N/A';
- const colorId = cur.c || 'none';
- out += `${nameHTML} ~ ${cur.id} | ${coords} | Color: ${colorId}\n`;
- }
- out += "\nClient ID: " + (w.clientId || "unknown");
- // --- HUD Management ---
- const hud = hudElement || createDraggableHUD(usernamesRaw);
- const selectEl = document.getElementById("userJumpSelect");
- const userListEl = document.getElementById('userListContent');
- // Update Dropdown
- if (selectEl) {
- const selectedValue = selectEl.value;
- selectEl.innerHTML = `<option value="">Teleport / Follow user…</option>`;
- usernamesRaw.forEach((name, i) => {
- const opt = el("option", { value: String(i), text: name });
- selectEl.appendChild(opt);
- });
- if (selectedValue !== "") {
- selectEl.value = selectedValue;
- } else if (followIndex >= 0 && followIndex < usernamesRaw.length) {
- selectEl.value = String(followIndex);
- }
- }
- // Update User List Content
- if (userListEl) userListEl.innerHTML = out;
- // TELEPORT / FOLLOW Logic
- if (utid >= 0 && utid < a.length) {
- const cur = a[utid][1];
- if (cur && cur.l && cur.l.length === 2 && typeof w.tp === "function") {
- w.tp(cur.l[0], cur.l[1]);
- }
- utid = -1;
- }
- if (followMode && followIndex >= 0 && followIndex < a.length) {
- const cur = a[followIndex][1];
- if (cur && cur.l && cur.l.length === 2 && typeof w.tp === "function") {
- w.tp(cur.l[0], cur.l[1]);
- }
- }
- } catch (err) {
- console.error("HUD loop error:", err);
- const userList = document.getElementById('userListContent');
- if (userList) userList.textContent = "HUD Error: " + err.message;
- }
- requestAnimationFrame(ut);
- }
- // ----------------------------------------------------
- // ⭐ INITIALIZATION
- // ----------------------------------------------------
- loadCustomThemes();
- ut();
Advertisement
Comments
-
Comment was deleted
-
- Update #1: Made the HUD collapsible.
-
- The hud should appear at the top left of the screen,
- under the nearby counter.
-
- Update #2: added a "follow user" toggle button
-
Comment was deleted
-
- A big thank you goes to AdjectiveNounNumber for the new HUD look!
-
- Update #4: you can now save up to 10 custom themes, theme editor added.
-
- Update #4.3: Options HUD added
Add Comment
Please, Sign In to add comment