smoothretro82

live movable small HUD that displays users & their positions (update 4.3)

Nov 19th, 2025 (edited)
550
0
Never
13
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 41.29 KB | None | 0 0
  1. // ==================================
  2. // Draggable HUDs Component (User Tracker & Separate Theme Editor & Options)
  3. // FIXED: Theme Editor now manages saving and loading custom themes
  4. // ADDED: Maximum of 10 custom themes enforced and user notified.
  5. // ADDED: Separate Options HUD component.
  6. // ==================================
  7.  
  8. let followMode = false;
  9. let followIndex = -1;
  10. let utid = -1;
  11.  
  12. // Global references for the HUD elements
  13. let hudElement = null;
  14. let themeEditorElement = null; 
  15. let optionsHudElement = null; // NEW: Options HUD reference 
  16.  
  17. // Global reference for the TEMPORARY theme being edited
  18. let editTheme = null; 
  19. // Tracks the key of the custom theme currently loaded into the editor
  20. let editingCustomKey = null; 
  21.  
  22. // The maximum number of custom themes allowed
  23. const MAX_CUSTOM_THEMES = 10; 
  24.  
  25. // ==================================
  26. // THEME DEFINITIONS (Built-in themes are kept separate)
  27. // ==================================
  28. const BUILT_IN_THEMES = {
  29.   dark: {
  30.     name: "Dark (Default)",
  31.     boxBg: "#222",
  32.     boxText: "#fff",
  33.     border: "#444",
  34.     headerBg: "#111",
  35.     selectBg: "#333",
  36.     selectText: "#fff",
  37.     toggleColor: "#fff",
  38.     custom: false
  39.   },
  40.   light: {
  41.     name: "Light (Default)",
  42.     boxBg: "#fafafa",
  43.     boxText: "#000",
  44.     border: "#ccc",
  45.     headerBg: "#e6e6e6",
  46.     selectBg: "#fff",
  47.     selectText: "#000",
  48.     toggleColor: "#000",
  49.     custom: false
  50.   },
  51.   win95: {
  52.     name: "Windows 95 (Default)",
  53.     boxBg: "#C0C0C0",
  54.     boxText: "#000000",
  55.     border: "#808080",
  56.     headerBg: "#000080",
  57.     selectBg: "#FFFFFF",
  58.     selectText: "#000000",
  59.     toggleColor: "#FFFFFF",
  60.     custom: false
  61.   },
  62. };
  63.  
  64. // Merged themes: BUILT_IN_THEMES + Custom Themes from localStorage
  65. let HUD_THEMES = JSON.parse(JSON.stringify(BUILT_IN_THEMES)); 
  66.  
  67. let currentTheme = "dark";
  68.  
  69. // ----------------------------------------------------
  70. // LocalStorage Functions
  71. // ----------------------------------------------------
  72.  
  73. function loadCustomThemes() {
  74.     try {
  75.         const storedThemes = localStorage.getItem('hudCustomThemes');
  76.         if (storedThemes) {
  77.             const customThemes = JSON.parse(storedThemes);
  78.             Object.assign(HUD_THEMES, customThemes);
  79.         }
  80.     } catch (e) {
  81.         console.error("Error loading custom themes from localStorage:", e);
  82.         HUD_THEMES = JSON.parse(JSON.stringify(BUILT_IN_THEMES)); 
  83.     }
  84. }
  85.  
  86. function saveCustomThemes() {
  87.     try {
  88.         const customThemesToSave = {};
  89.         
  90.         // 1. Identify all custom themes
  91.         for (const key in HUD_THEMES) {
  92.             if (HUD_THEMES[key].custom) {
  93.                 customThemesToSave[key] = HUD_THEMES[key];
  94.             }
  95.         }
  96.  
  97.         // 2. Save the custom themes (no FIFO deletion needed anymore, 
  98.         //    as the save button logic now prevents exceeding the limit).
  99.         localStorage.setItem('hudCustomThemes', JSON.stringify(customThemesToSave));
  100.         console.log(`Custom themes saved. Total active custom themes: ${Object.keys(customThemesToSave).length}`);
  101.  
  102.     } catch (e) {
  103.         console.error("Error saving custom themes to localStorage:", e);
  104.     }
  105. }
  106.  
  107. // ----------------------------------------------------
  108. // Utility Functions
  109. // ----------------------------------------------------
  110.  
  111. // Utility: safe create element with props
  112. function el(name, props = {}) {
  113.   const e = document.createElement(name);
  114.   Object.keys(props).forEach(k => {
  115.     if (k === "style") {
  116.       Object.assign(e.style, props.style);
  117.     } else if (k === "html") {
  118.       e.innerHTML = props.html;
  119.     } else if (k === "text") {
  120.       e.textContent = props.text;
  121.     } else {
  122.       e.setAttribute(k, props[k]);
  123.     }
  124.   });
  125.   return e;
  126. }
  127.  
  128. // Utility: Rebuilds the main HUD theme selection dropdown
  129. function populateThemeSelect(selectEl, selectedKey) {
  130.     selectEl.innerHTML = '';
  131.     
  132.     // Add Built-in themes
  133.     const builtInGroup = el('optgroup', { label: 'Built-in Themes' });
  134.     for (const key in BUILT_IN_THEMES) {
  135.         const opt = el("option", { value: key, text: BUILT_IN_THEMES[key].name });
  136.         builtInGroup.appendChild(opt);
  137.     }
  138.     selectEl.appendChild(builtInGroup);
  139.  
  140.     // Add Custom themes
  141.     const customThemeKeys = Object.keys(HUD_THEMES).filter(key => HUD_THEMES[key].custom);
  142.     if (customThemeKeys.length > 0) {
  143.         const customGroup = el('optgroup', { label: 'Custom Themes' });
  144.         customThemeKeys.forEach(key => {
  145.             const opt = el("option", { value: key, text: HUD_THEMES[key].name });
  146.             customGroup.appendChild(opt);
  147.         });
  148.         selectEl.appendChild(customGroup);
  149.     }
  150.  
  151.     selectEl.value = selectedKey || 'dark';
  152. }
  153.  
  154. // Utility: Rebuilds the Editor's custom theme selection dropdown
  155. function populateEditorThemeSelect(selectEl, selectedKey) {
  156.     selectEl.innerHTML = '';
  157.     
  158.     // Always include the "New Theme" option
  159.     const newOpt = el("option", { value: 'NEW', text: '-- Create New Theme --' });
  160.     selectEl.appendChild(newOpt);
  161.  
  162.     // Add Custom themes
  163.     const customThemeKeys = Object.keys(HUD_THEMES).filter(key => HUD_THEMES[key].custom);
  164.     if (customThemeKeys.length > 0) {
  165.         // Sort keys by name for a cleaner list
  166.         customThemeKeys.sort((a, b) => HUD_THEMES[a].name.localeCompare(HUD_THEMES[b].name));
  167.         
  168.         customThemeKeys.forEach(key => {
  169.             const opt = el("option", { value: key, text: HUD_THEMES[key].name.replace(' (Custom)', '') });
  170.             selectEl.appendChild(opt);
  171.         });
  172.     }
  173.  
  174.     selectEl.value = selectedKey || 'NEW';
  175. }
  176.  
  177.  
  178. // ----------------------------------------------------
  179. // Custom Theme Application Function for all HUDS
  180. // ----------------------------------------------------
  181. function applyHUDTheme(themeKeyOrObject) {
  182.   const t = editTheme 
  183.     ? editTheme
  184.     : (typeof themeKeyOrObject === "object" && themeKeyOrObject !== null
  185.       ? themeKeyOrObject
  186.       : HUD_THEMES[currentTheme] || HUD_THEMES.dark);
  187.  
  188.   // Apply to Main HUD
  189.   const hud = document.getElementById('draggable-hud');
  190.   if (hud) {
  191.       const header = document.getElementById('hud-header');
  192.       const content = document.getElementById('hud-content');
  193.       const minBtn = document.getElementById('hud-min-btn');
  194.       const select = document.getElementById('userJumpSelect');
  195.       const themeSel = document.getElementById('themeSelect');
  196.       const editorBtn = document.getElementById('openEditorBtn'); 
  197.       const optionsBtn = document.getElementById('openOptionsBtn'); // NEW
  198.  
  199.       hud.style.backgroundColor = t.boxBg;
  200.       hud.style.color = t.boxText;
  201.       hud.style.border = `1px solid ${t.border}`;
  202.       if (header) {
  203.           header.style.backgroundColor = t.headerBg;
  204.           header.style.color = t.boxText;
  205.       }
  206.       if (content) content.style.color = t.boxText;
  207.       if (minBtn) minBtn.style.color = t.toggleColor; 
  208.       if (select) {
  209.           select.style.background = t.selectBg || t.boxBg;
  210.           select.style.color = t.selectText || t.boxText;
  211.           select.style.border = `1px solid ${t.border}`;
  212.       }
  213.       if (themeSel) {
  214.           themeSel.style.background = t.selectBg || t.boxBg;
  215.           themeSel.style.color = t.selectText || t.boxText;
  216.           themeSel.style.border = `1px solid ${t.border}`;
  217.           if (!editTheme && typeof themeKeyOrObject !== "object") {
  218.               themeSel.value = currentTheme;
  219.           }
  220.       }
  221.       if (editorBtn) {
  222.            editorBtn.style.background = t.selectBg || t.boxBg;
  223.            editorBtn.style.color = t.selectText || t.boxText;
  224.            editorBtn.style.border = `1px solid ${t.border}`;
  225.       }
  226.       if (optionsBtn) { // NEW
  227.            optionsBtn.style.background = t.selectBg || t.boxBg;
  228.            optionsBtn.style.color = t.selectText || t.boxText;
  229.            optionsBtn.style.border = `1px solid ${t.border}`;
  230.       }
  231.   }
  232.  
  233.   // Apply to Editor HUD if it exists
  234.   const editorHud = document.getElementById('theme-editor-hud');
  235.   if (editorHud) {
  236.       const editorHeader = document.getElementById('editor-hud-header');
  237.       const deleteBtn = document.getElementById('deleteCustomThemeBtn');
  238.  
  239.       editorHud.style.backgroundColor = t.boxBg;
  240.       editorHud.style.color = t.boxText;
  241.       editorHud.style.border = `1px solid ${t.border}`;
  242.       if (editorHeader) {
  243.           editorHeader.style.backgroundColor = t.headerBg;
  244.           editorHeader.style.color = t.boxText;
  245.           editorHeader.style.borderBottom = `1px solid ${t.border}`;
  246.       }
  247.       
  248.       // Update delete button visibility based on the theme loaded into the editor
  249.       if (deleteBtn) {
  250.           deleteBtn.style.display = editingCustomKey ? 'inline-block' : 'none';
  251.       }
  252.   }
  253.  
  254.   // NEW: Apply to Options HUD if it exists
  255.   const optionsHud = document.getElementById('options-hud');
  256.   if (optionsHud) {
  257.       const optionsHeader = document.getElementById('options-hud-header');
  258.       const optionsMinBtn = document.getElementById('options-min-btn');
  259.  
  260.       optionsHud.style.backgroundColor = t.boxBg;
  261.       optionsHud.style.color = t.boxText;
  262.       optionsHud.style.border = `1px solid ${t.border}`;
  263.       if (optionsHeader) {
  264.           optionsHeader.style.backgroundColor = t.headerBg;
  265.           optionsHeader.style.color = t.boxText;
  266.           optionsHeader.style.borderBottom = `1px solid ${t.border}`;
  267.       }
  268.       if (optionsMinBtn) optionsMinBtn.style.color = t.toggleColor;
  269.   }
  270.  
  271.   // Update editor inputs if theme changes from dropdown (not from editor)
  272.   if (!editTheme) {
  273.     updateThemeEditorInputs(t);
  274.   }
  275. }
  276.  
  277. // Helper to sync editor inputs with the current active theme
  278. function updateThemeEditorInputs(theme) {
  279.     const editorEl = document.getElementById('theme-editor-hud');
  280.     if (!editorEl) return;
  281.     
  282.     // Update theme name input
  283.     const themeNameInput = document.getElementById('edit_name');
  284.     if (themeNameInput) {
  285.         // Remove ' (Custom)' or ' (Default)' before showing in the editor input
  286.         themeNameInput.value = theme.name.replace(' (Custom)', '').replace(' (Default)', '');
  287.     }
  288.  
  289.     // Update color inputs
  290.     document.getElementById('edit_boxBg').value = theme.boxBg || '#000000';
  291.     document.getElementById('edit_boxText').value = theme.boxText || '#FFFFFF';
  292.     document.getElementById('edit_border').value = theme.border || '#999999';
  293.     document.getElementById('edit_headerBg').value = theme.headerBg || '#000000';
  294.     document.getElementById('edit_selectBg').value = theme.selectBg || theme.boxBg || '#000000';
  295.     document.getElementById('edit_selectText').value = theme.selectText || theme.boxText || '#FFFFFF';
  296.     document.getElementById('edit_toggleColor').value = theme.toggleColor || theme.boxText || '#FFFFFF';
  297. }
  298.  
  299. /**
  300.  * Loads a theme into the editor.
  301.  * @param {string} key - The key of the theme to load (built-in or custom).
  302.  */
  303. function loadThemeIntoEditor(key) {
  304.     const themeToLoad = HUD_THEMES[key] || HUD_THEMES.dark;
  305.     
  306.     // 1. Set the editor state variables
  307.     editTheme = JSON.parse(JSON.stringify(themeToLoad)); // Create a copy for live editing
  308.     editingCustomKey = themeToLoad.custom ? key : null;
  309.     
  310.     // 2. Update editor inputs and apply theme preview
  311.     updateThemeEditorInputs(editTheme);
  312.     applyHUDTheme(editTheme); // Apply the editTheme preview
  313.     
  314.     // 3. Update the button states
  315.     const saveButton = document.getElementById('saveThemeBtn');
  316.     if (saveButton) {
  317.         saveButton.textContent = editingCustomKey ? "Update Theme" : "Save As New";
  318.     }
  319. }
  320.  
  321.  
  322. // ----------------------------------------------------
  323. // Theme Editor HUD Creation
  324. // ----------------------------------------------------
  325.  
  326. function createThemeEditorHUD() {
  327.     if (themeEditorElement) {
  328.         return themeEditorElement;
  329.     }
  330.     
  331.     // Initialize the editor with the currently applied theme
  332.     loadThemeIntoEditor(currentTheme); 
  333.     
  334.     // --- HUD Element Setup ---
  335.     const hud = document.createElement('div');
  336.     hud.id = 'theme-editor-hud';
  337.     Object.assign(hud.style, {
  338.         position: 'absolute',
  339.         backgroundColor: HUD_THEMES[currentTheme].boxBg,
  340.         border: `1px solid ${HUD_THEMES[currentTheme].border}`,
  341.         borderRadius: '8px',
  342.         width: '320px', // Slightly wider for new controls
  343.         zIndex: '1001', 
  344.         top: '100px', 
  345.         left: '420px', 
  346.         boxShadow: '2px 2px 5px rgba(0,0,0,0.2)',
  347.         fontFamily: 'monospace',
  348.         fontSize: '11px',
  349.         overflow: 'hidden',
  350.     });
  351.  
  352.     // --- Header Setup ---
  353.     const header = document.createElement('div');
  354.     header.id = 'editor-hud-header';
  355.     Object.assign(header.style, {
  356.         backgroundColor: HUD_THEMES[currentTheme].headerBg,
  357.         height: '25px',
  358.         cursor: 'grab',
  359.         display: 'flex',
  360.         borderRadius: '8px 8px 0px 0px',
  361.         justifyContent: 'space-between',
  362.         alignItems: 'center',
  363.         color: HUD_THEMES[currentTheme].boxText,
  364.         paddingLeft: '5px',
  365.         userSelect: 'none'
  366.     });
  367.     header.textContent = 'Theme Editor';
  368.  
  369.     // --- Close Button ---
  370.     const closeButton = document.createElement('span');
  371.     closeButton.textContent = 'X';
  372.     Object.assign(closeButton.style, {
  373.         cursor: 'pointer',
  374.         padding: '0 5px',
  375.         fontWeight: 'bold',
  376.         color: HUD_THEMES[currentTheme].toggleColor,
  377.         fontSize: '14px'
  378.     });
  379.     closeButton.addEventListener('click', () => {
  380.         hud.remove();
  381.         themeEditorElement = null;
  382.         editTheme = null; 
  383.         editingCustomKey = null;
  384.         applyHUDTheme(); // Revert preview to the actual currentTheme
  385.     });
  386.  
  387.     header.appendChild(closeButton);
  388.     hud.appendChild(header);
  389.  
  390.     // --- Content Area (Editor Controls) ---
  391.     const content = document.createElement('div');
  392.     content.id = 'editor-hud-content';
  393.     Object.assign(content.style, {
  394.         padding: '10px',
  395.         display: 'flex',
  396.         flexDirection: 'column'
  397.     });
  398.     
  399.     // Custom Theme Selection Dropdown
  400.     const themeSelectWrap = el("div", { 
  401.         style: { marginBottom: "8px", borderBottom: `1px solid ${HUD_THEMES.dark.border}`, paddingBottom: '8px' } 
  402.     });
  403.     const selectLabel = el("label", { text: "Load Custom Theme:", style: { fontSize: "10px", display: "block", marginBottom: "2px" } });
  404.     const editorThemeSel = el("select", {
  405.       id: "editorThemeSelect",
  406.       style: { width: "100%", padding: "2px", border: "1px solid #888" }
  407.     });
  408.     
  409.     populateEditorThemeSelect(editorThemeSel, editingCustomKey || 'NEW');
  410.  
  411.     editorThemeSel.addEventListener("change", (e) => {
  412.         const key = e.target.value;
  413.         if (key === 'NEW') {
  414.             // Load the current main theme for starting a new custom theme
  415.             loadThemeIntoEditor(currentTheme);
  416.         } else {
  417.             // Load the selected custom theme for editing
  418.             loadThemeIntoEditor(key);
  419.         }
  420.     });
  421.     
  422.     themeSelectWrap.appendChild(selectLabel);
  423.     themeSelectWrap.appendChild(editorThemeSel);
  424.     content.appendChild(themeSelectWrap);
  425.  
  426.  
  427.     // Theme Name Input
  428.     const nameWrap = el("div", { style: { marginBottom: "8px", display: "flex", alignItems: "center" } });
  429.     const nameLabel = el("label", { text: "Theme Name:", style: { fontSize: "10px", marginRight: "8px" } });
  430.     const nameInput = el("input", {
  431.         type: 'text', 
  432.         id: 'edit_name',
  433.         // Value is set by loadThemeIntoEditor
  434.         style: { width: "100%", padding: "2px", border: "1px solid #888" }
  435.     });
  436.     nameWrap.appendChild(nameLabel);
  437.     nameWrap.appendChild(nameInput);
  438.     content.appendChild(nameWrap);
  439.  
  440.  
  441.     // Theme Keys and Grid Setup
  442.     const themeKeys = Object.keys(BUILT_IN_THEMES.dark).filter(k => k !== 'name' && k !== 'custom'); 
  443.     const grid = el("div", {
  444.         style: {
  445.             display: "grid",
  446.             gridTemplateColumns: "1fr 1fr",
  447.             gap: "4px 8px"
  448.         }
  449.     });
  450.  
  451.     themeKeys.forEach(key => {
  452.         const labelText = key.replace(/([A-Z])/g, ' $1').replace(/^box/, 'HUD').replace(/^header/, 'Header').replace(/^select/, 'Select').trim();
  453.         
  454.         const label = el("label", {
  455.             text: labelText + ":",
  456.             style: { display: "block", fontSize: "10px", textAlign: "right" }
  457.         });
  458.  
  459.         const input = el("input", {
  460.             type: 'color', 
  461.             id: `edit_${key}`,
  462.             // Value is set by loadThemeIntoEditor
  463.             style: { width: "100%", padding: "1px", border: "1px solid #888" }
  464.         });
  465.         
  466.         input.addEventListener('input', (e) => {
  467.             // Update the live preview theme (editTheme)
  468.             if (!editTheme) {
  469.                 editTheme = JSON.parse(JSON.stringify(HUD_THEMES[currentTheme]));
  470.             }
  471.             editTheme[key] = e.target.value;
  472.             applyHUDTheme(editTheme); 
  473.         });
  474.  
  475.         grid.appendChild(label);
  476.         grid.appendChild(input);
  477.     });
  478.  
  479.     content.appendChild(grid);
  480.  
  481.     // Reset and Apply/Save Buttons
  482.     const actionsBottom = el("div", {
  483.         style: { 
  484.             display: "flex", 
  485.             justifyContent: "space-between", 
  486.             marginTop: "10px", 
  487.             paddingTop: "6px", 
  488.             borderTop: `1px solid ${HUD_THEMES.dark.border}`,
  489.         }
  490.     });
  491.     
  492.     // Reset to initial editor load state
  493.     const resetButton = el("button", {
  494.         text: "Reset Edits",
  495.         style: { padding: "4px 8px", cursor: "pointer", flexGrow: 1, marginRight: "4px", fontSize: '10px' }
  496.     });
  497.     resetButton.addEventListener('click', () => {
  498.         // Reloads the theme that was originally opened in the editor
  499.         loadThemeIntoEditor(editingCustomKey || currentTheme);
  500.     });
  501.     
  502.     // Save/Update Button
  503.     const saveButton = el("button", {
  504.         id: "saveThemeBtn",
  505.         text: editingCustomKey ? "Update Theme" : "Save As New",
  506.         style: { 
  507.             padding: "4px 8px", 
  508.             cursor: "pointer", 
  509.             flexGrow: 1, 
  510.             marginLeft: "4px",
  511.             fontSize: '10px',
  512.             fontWeight: 'bold'
  513.         }
  514.     });
  515.  
  516.     saveButton.addEventListener('click', () => {
  517.         // Use the temporary live editTheme for saving
  518.         if (!editTheme) return; 
  519.  
  520.         const customThemeKeys = Object.keys(HUD_THEMES).filter(key => HUD_THEMES[key].custom);
  521.  
  522.         if (!editingCustomKey && customThemeKeys.length >= MAX_CUSTOM_THEMES) {
  523.             alert(`🛑 Cannot save new theme. You have reached the maximum limit of ${MAX_CUSTOM_THEMES} custom themes. Please delete an existing custom theme first.`);
  524.             return; // STOP save operation
  525.         }
  526.  
  527.         const newName = document.getElementById('edit_name').value.trim() || 'Custom Theme';
  528.         let keyToSave;
  529.  
  530.         if (editingCustomKey) {
  531.             // --- UPDATE EXISTING THEME ---
  532.             keyToSave = editingCustomKey;
  533.         } else {
  534.             // --- SAVE AS NEW THEME ---
  535.             keyToSave = 'custom_' + Date.now();
  536.         }
  537.  
  538.         // Apply final changes and mark as custom
  539.         editTheme.name = newName + ' (Custom)';
  540.         editTheme.custom = true;
  541.         
  542.         // Save to global list
  543.         HUD_THEMES[keyToSave] = editTheme;
  544.  
  545.         // 1. Save to storage
  546.         saveCustomThemes();
  547.         
  548.         // 2. Set the saved theme as the current theme
  549.         currentTheme = keyToSave;
  550.  
  551.         // 3. Update all dropdowns (main HUD and editor HUD)
  552.         const themeSelMain = document.getElementById('themeSelect');
  553.         if(themeSelMain) populateThemeSelect(themeSelMain, currentTheme); 
  554.         
  555.         populateEditorThemeSelect(document.getElementById('editorThemeSelect'), keyToSave);
  556.         
  557.         // 4. Reset editor state to reflect the saved theme
  558.         loadThemeIntoEditor(keyToSave);
  559.         
  560.         alert(`Theme saved successfully as: ${newName}!`);
  561.     });
  562.  
  563.     actionsBottom.appendChild(resetButton);
  564.     actionsBottom.appendChild(saveButton);
  565.     
  566.     // Delete Button
  567.     const deleteButton = el("button", {
  568.         id: "deleteCustomThemeBtn",
  569.         text: "Delete Theme",
  570.         style: { 
  571.             padding: "4px 8px", 
  572.             cursor: "pointer", 
  573.             flexGrow: 1, 
  574.             marginTop: "4px",
  575.             fontSize: '10px',
  576.             backgroundColor: '#ff9999',
  577.             // Display is toggled in applyHUDTheme based on editingCustomKey
  578.         }
  579.     });
  580.     deleteButton.addEventListener('click', () => {
  581.         if (!editingCustomKey) return; 
  582.  
  583.         if (confirm(`Are you sure you want to delete the theme "${HUD_THEMES[editingCustomKey].name}"?`)) {
  584.             
  585.             // Delete from the merged object
  586.             delete HUD_THEMES[editingCustomKey];
  587.             
  588.             // 1. Save state
  589.             saveCustomThemes();
  590.  
  591.             // 2. Revert to a default theme if the deleted one was current
  592.             if (currentTheme === editingCustomKey) {
  593.                 currentTheme = 'dark';
  594.             }
  595.             
  596.             // 3. Update all dropdowns
  597.             const themeSelMain = document.getElementById('themeSelect');
  598.             if(themeSelMain) populateThemeSelect(themeSelMain, currentTheme);
  599.             
  600.             // 4. Reset the editor to "New Theme" state
  601.             loadThemeIntoEditor(currentTheme); 
  602.             populateEditorThemeSelect(document.getElementById('editorThemeSelect'), 'NEW');
  603.         }
  604.     });
  605.  
  606.  
  607.     content.appendChild(actionsBottom);
  608.     content.appendChild(deleteButton); // Add the delete button below the main actions
  609.  
  610.     hud.appendChild(content);
  611.     document.body.appendChild(hud);
  612.     themeEditorElement = hud;
  613.     
  614.     // Final sync after all elements are attached
  615.     applyHUDTheme(); 
  616.     
  617.     // --- Drag Logic --- (Standard drag implementation)
  618.     let isDragging = false;
  619.     let offsetX, offsetY;
  620.     header.addEventListener('mousedown', (e) => {
  621.         if (e.target === closeButton) return;
  622.         isDragging = true;
  623.         offsetX = e.clientX - hud.getBoundingClientRect().left;
  624.         offsetY = e.clientY - hud.getBoundingClientRect().top;
  625.         header.style.cursor = 'grabbing';
  626.         document.body.style.userSelect = 'none';
  627.     });
  628.     document.addEventListener('mousemove', (e) => {
  629.         if (!isDragging) return;
  630.         hud.style.left = (e.clientX - offsetX) + 'px';
  631.         hud.style.top = (e.clientY - offsetY) + 'px';
  632.     });
  633.     document.addEventListener('mouseup', () => {
  634.         isDragging = false;
  635.         header.style.cursor = 'grab';
  636.         document.body.style.userSelect = '';
  637.     });
  638.  
  639.     return hud;
  640. }
  641.  
  642. // ----------------------------------------------------
  643. // Options HUD Component Creation (NEW FUNCTION)
  644. // ----------------------------------------------------
  645. function createOptionsHUD() {
  646.     if (optionsHudElement) {
  647.       return optionsHudElement; 
  648.     }
  649.     
  650.     // --- HUD Element Setup ---
  651.     const hud = document.createElement('div');
  652.     hud.id = 'options-hud';
  653.     const initialTheme = HUD_THEMES[currentTheme];
  654.     Object.assign(hud.style, {
  655.         position: 'absolute',
  656.         backgroundColor: initialTheme.boxBg,
  657.         border: `1px solid ${initialTheme.border}`,
  658.         borderRadius: '8px',
  659.         width: '250px', // Smaller size
  660.         height: '150px', 
  661.         zIndex: '1002', // Higher Z-index than main HUD (1000)
  662.         top: '150px', 
  663.         left: '50px',
  664.         boxShadow: '2px 2px 5px rgba(0,0,0,0.2)',
  665.         fontFamily: 'monospace',
  666.         fontSize: '11px',
  667.         overflow: 'hidden',
  668.         transition: 'height 0.2s ease',
  669.         color: initialTheme.boxText
  670.     });
  671.  
  672.     // --- Header Setup ---
  673.     const header = document.createElement('div');
  674.     header.id = 'options-hud-header';
  675.     Object.assign(header.style, {
  676.         backgroundColor: initialTheme.headerBg,
  677.         height: '25px',
  678.         cursor: 'grab',
  679.         display: 'flex',
  680.         borderRadius: '8px 8px 0px 0px',
  681.         justifyContent: 'space-between',
  682.         alignItems: 'center',
  683.         color: initialTheme.boxText,
  684.         paddingLeft: '5px',
  685.         userSelect: 'none'
  686.     });
  687.     header.textContent = 'Options Menu';
  688.  
  689.     // --- Button Container ---
  690.     const buttonContainer = document.createElement('div');
  691.     buttonContainer.style.display = 'flex';
  692.  
  693.     // --- Minimize Button ---
  694.     const minimizeButton = document.createElement('span');
  695.     minimizeButton.id = 'options-min-btn';
  696.     minimizeButton.textContent = '-';
  697.     Object.assign(minimizeButton.style, {
  698.         cursor: 'pointer',
  699.         padding: '0 5px',
  700.         fontWeight: 'bold',
  701.         color: initialTheme.toggleColor,
  702.         fontSize: '18px'
  703.     });
  704.     buttonContainer.appendChild(minimizeButton);
  705.  
  706.     // --- Close Button ---
  707.     const closeButton = document.createElement('span');
  708.     closeButton.textContent = 'X';
  709.     Object.assign(closeButton.style, {
  710.         cursor: 'pointer',
  711.         padding: '0 5px',
  712.         fontWeight: 'bold',
  713.         color: initialTheme.toggleColor,
  714.         fontSize: '14px'
  715.     });
  716.     closeButton.addEventListener('click', () => {
  717.         hud.remove();
  718.         optionsHudElement = null;
  719.     });
  720.     buttonContainer.appendChild(closeButton);
  721.  
  722.     header.appendChild(buttonContainer);
  723.     hud.appendChild(header);
  724.  
  725.     // --- Content Area (Currently Empty) ---
  726.     const content = document.createElement('div');
  727.     content.id = 'options-hud-content';
  728.     Object.assign(content.style, {
  729.         padding: '10px',
  730.         height: 'calc(100% - 25px)',
  731.         overflow: 'auto',
  732.         display: 'flex',
  733.         flexDirection: 'column'
  734.     });
  735.     content.textContent = "Options content goes here..."; // Placeholder text
  736.  
  737.     hud.appendChild(content);
  738.     document.body.appendChild(hud);
  739.     optionsHudElement = hud;
  740.  
  741.     // --- Drag & Minimize Logic ---
  742.     let isDragging = false;
  743.     let offsetX, offsetY;
  744.     const originalHeight = '150px';
  745.     const headerHeight = '25px';
  746.  
  747.     header.addEventListener('mousedown', (e) => {
  748.         if (e.target.tagName !== 'SPAN') { // Don't drag when clicking buttons
  749.             isDragging = true;
  750.             offsetX = e.clientX - hud.getBoundingClientRect().left;
  751.             offsetY = e.clientY - hud.getBoundingClientRect().top;
  752.             header.style.cursor = 'grabbing';
  753.             document.body.style.userSelect = 'none';
  754.         }
  755.     });
  756.     document.addEventListener('mousemove', (e) => {
  757.         if (!isDragging) return;
  758.         hud.style.left = (e.clientX - offsetX) + 'px';
  759.         hud.style.top = (e.clientY - offsetY) + 'px';
  760.     });
  761.     document.addEventListener('mouseup', () => {
  762.         isDragging = false;
  763.         header.style.cursor = 'grab';
  764.         document.body.style.userSelect = '';
  765.     });
  766.  
  767.     minimizeButton.addEventListener('click', () => {
  768.         if (content.style.display === 'none') {
  769.             content.style.display = 'flex';
  770.             hud.style.height = originalHeight;
  771.             minimizeButton.textContent = '-';
  772.         } else {
  773.             content.style.display = 'none';
  774.             hud.style.height = headerHeight;
  775.             minimizeButton.textContent = '+';
  776.         }
  777.     });
  778.  
  779.     return hud;
  780. }
  781.  
  782.  
  783. // ----------------------------------------------------
  784. // Main HUD Component Creation (User Tracker)
  785. // ----------------------------------------------------
  786. function createDraggableHUD(usernamesRaw) {
  787.     if (hudElement) {
  788.       return hudElement; 
  789.     }
  790.     
  791.     // --- HUD Element Setup ---
  792.     const hud = document.createElement('div');
  793.     hud.id = 'draggable-hud';
  794.     Object.assign(hud.style, {
  795.         position: 'absolute',
  796.         backgroundColor: 'lightgray',
  797.         border: '1px solid black',
  798.         borderRadius: '8px',
  799.         width: '350px', 
  800.         height: '300px', 
  801.         zIndex: '1000',
  802.         top: '50px', 
  803.         left: '50px',
  804.         boxShadow: '2px 2px 5px rgba(0,0,0,0.2)',
  805.         fontFamily: 'monospace',
  806.         fontSize: '11px',
  807.         overflow: 'hidden',
  808.         transition: 'height 0.2s ease'
  809.     });
  810.  
  811.     // --- Header Setup ---
  812.     const header = document.createElement('div');
  813.     header.id = 'hud-header';
  814.     Object.assign(header.style, {
  815.         backgroundColor: 'gray',
  816.         height: '25px',
  817.         cursor: 'grab',
  818.         display: 'flex',
  819.         borderRadius: '8px 8px 0px 0px',
  820.         justifyContent: 'space-between',
  821.         alignItems: 'center',
  822.         color: 'white',
  823.         paddingLeft: '5px',
  824.         userSelect: 'none'
  825.     });
  826.     header.textContent = 'User Tracker HUD';
  827.  
  828.     // --- Button Container ---
  829.     const buttonContainer = document.createElement('div');
  830.     buttonContainer.style.display = 'flex';
  831.  
  832.     // --- Minimize Button ---
  833.     const minimizeButton = document.createElement('span');
  834.     minimizeButton.id = 'hud-min-btn';
  835.     minimizeButton.textContent = '-';
  836.     Object.assign(minimizeButton.style, {
  837.         cursor: 'pointer',
  838.         padding: '0 5px',
  839.         fontWeight: 'bold',
  840.         color: 'white',
  841.         fontSize: '18px'
  842.     });
  843.     buttonContainer.appendChild(minimizeButton);
  844.  
  845.     // --- Close Button ---
  846.     const closeButton = document.createElement('span');
  847.     closeButton.textContent = 'X';
  848.     Object.assign(closeButton.style, {
  849.         cursor: 'pointer',
  850.         padding: '0 5px',
  851.         fontWeight: 'bold',
  852.         color: 'white',
  853.         fontSize: '14px'
  854.     });
  855.     closeButton.addEventListener('click', () => {
  856.         hud.remove();
  857.         hudElement = null;
  858.         if (themeEditorElement) { 
  859.             themeEditorElement.remove();
  860.             themeEditorElement = null;
  861.             editingCustomKey = null;
  862.             editTheme = null;
  863.         }
  864.         if (optionsHudElement) { // NEW: Close Options HUD as well
  865.             optionsHudElement.remove();
  866.             optionsHudElement = null;
  867.         }
  868.     });
  869.     buttonContainer.appendChild(closeButton);
  870.  
  871.     header.appendChild(buttonContainer);
  872.     hud.appendChild(header);
  873.  
  874.     // --- Content Area ---
  875.     const content = document.createElement('div');
  876.     content.id = 'hud-content';
  877.     Object.assign(content.style, {
  878.         padding: '5px 10px',
  879.         height: 'calc(100% - 25px)',
  880.         overflow: 'hidden',
  881.         display: 'flex',
  882.         flexDirection: 'column'
  883.     });
  884.  
  885.     // Controls container
  886.     const controls = el("div", {
  887.         style: { marginBottom: "8px", borderBottom: '1px dashed #999', paddingBottom: '5px', flexShrink: 0 }
  888.     });
  889.  
  890.     // Select (Teleport / Follow)
  891.     const select = el("select", {
  892.       id: "userJumpSelect",
  893.       style: { width: "100%", margin: "4px 0", padding: "2px" }
  894.     });
  895.     select.addEventListener("change", () => {
  896.       const idx = parseInt(select.value, 10);
  897.       if (!isNaN(idx)) {
  898.         utid = idx;
  899.         if (followMode) followIndex = idx;
  900.       }
  901.     });
  902.     
  903.     // Follow checkbox
  904.     const followWrap = el("label", {
  905.       style: { display: "flex", alignItems: "center", gap: "6px", margin: "6px 0" }
  906.     });
  907.     const followCB = el("input", { type: "checkbox", id: "followCheck" });
  908.     followCB.checked = followMode;
  909.     followCB.addEventListener("change", () => {
  910.       followMode = followCB.checked;
  911.       if (followMode) {
  912.         const idx = parseInt(select.value, 10);
  913.         if (!isNaN(idx)) followIndex = idx;
  914.       } else {
  915.         followIndex = -1;
  916.       }
  917.     });
  918.     const followLabel = el("span", { text: "Follow user" });
  919.     followWrap.appendChild(followCB);
  920.     followWrap.appendChild(followLabel);
  921.  
  922.     // Theme selector dropdown (Main HUD)
  923.     const themeSel = el("select", {
  924.       id: "themeSelect",
  925.       style: { width: "calc(35% - 4px)", margin: "4px 0", padding: "2px", display: 'inline-block', verticalAlign: 'top', marginRight: '4px' }
  926.     });
  927.     
  928.     // Populate the main theme select
  929.     populateThemeSelect(themeSel, currentTheme);
  930.  
  931.     themeSel.addEventListener("change", () => {
  932.       currentTheme = themeSel.value;
  933.       editTheme = null; 
  934.       applyHUDTheme();
  935.     });
  936.  
  937.     // Theme Editor Button
  938.     const editorButton = el("button", {
  939.         id: "openEditorBtn",
  940.         text: "Edit Theme",
  941.         style: {
  942.             width: "35%",
  943.             padding: "2px",
  944.             margin: "4px 0",
  945.             cursor: "pointer",
  946.             display: 'inline-block',
  947.             verticalAlign: 'top'
  948.         }
  949.     });
  950.     editorButton.addEventListener('click', () => {
  951.         if (!themeEditorElement) {
  952.             createThemeEditorHUD();
  953.         } else {
  954.             themeEditorElement.remove();
  955.             themeEditorElement = null;
  956.             editingCustomKey = null;
  957.             editTheme = null; 
  958.             applyHUDTheme(); 
  959.         }
  960.     });
  961.    
  962.     // NEW: Options Button
  963.     const optionsButton = el("button", {
  964.         id: "openOptionsBtn",
  965.         text: "Options",
  966.         style: {
  967.             width: "30%", 
  968.             padding: "2px",
  969.             margin: "4px 0",
  970.             cursor: "pointer",
  971.             display: 'inline-block',
  972.             verticalAlign: 'top',
  973.             marginLeft: '4px' // Add spacing
  974.         }
  975.     });
  976.     optionsButton.addEventListener('click', () => {
  977.         if (!optionsHudElement) {
  978.             createOptionsHUD();
  979.         } else {
  980.             optionsHudElement.remove();
  981.             optionsHudElement = null;
  982.         }
  983.     });
  984.  
  985.  
  986.     controls.appendChild(select);
  987.     controls.appendChild(followWrap);
  988.     
  989.     const themeControls = el("div", {
  990.         style: { display: 'flex', justifyContent: 'space-between'}
  991.     });
  992.     themeControls.appendChild(themeSel);
  993.     themeControls.appendChild(editorButton);
  994.     themeControls.appendChild(optionsButton); // APPEND THE NEW BUTTON
  995.     controls.appendChild(themeControls);
  996.  
  997.     content.appendChild(controls);
  998.  
  999.     // User List Area
  1000.     const userList = el("div", { 
  1001.         id: 'userListContent', 
  1002.         style: { 
  1003.             whiteSpace: 'pre',
  1004.             overflowX: 'auto',
  1005.             overflowY: 'auto', 
  1006.             flexGrow: 1,
  1007.             marginTop: '5px' 
  1008.         }
  1009.     });
  1010.     content.appendChild(userList);
  1011.  
  1012.     hud.appendChild(content);
  1013.     document.body.appendChild(hud);
  1014.     
  1015.     hudElement = hud;
  1016.  
  1017.     // --- Drag Logic Variables/Listeners (omitted for brevity) ---
  1018.     let isDragging = false;
  1019.     let offsetX, offsetY;
  1020.     const originalHeight = '300px'; 
  1021.     const headerHeight = '25px';
  1022.  
  1023.     header.addEventListener('mousedown', (e) => {
  1024.         if (e.target === closeButton || e.target === minimizeButton) return;
  1025.         isDragging = true;
  1026.         offsetX = e.clientX - hud.getBoundingClientRect().left;
  1027.         offsetY = e.clientY - hud.getBoundingClientRect().top;
  1028.         header.style.cursor = 'grabbing';
  1029.         document.body.style.userSelect = 'none';
  1030.     });
  1031.  
  1032.     document.addEventListener('mousemove', (e) => {
  1033.         if (!isDragging) return;
  1034.         hud.style.left = (e.clientX - offsetX) + 'px';
  1035.         hud.style.top = (e.clientY - offsetY) + 'px';
  1036.     });
  1037.  
  1038.     document.addEventListener('mouseup', () => {
  1039.         isDragging = false;
  1040.         header.style.cursor = 'grab';
  1041.         document.body.style.userSelect = '';
  1042.     });
  1043.  
  1044.     minimizeButton.addEventListener('click', () => {
  1045.         if (content.style.display === 'none') {
  1046.             content.style.display = 'flex'; 
  1047.             hud.style.height = originalHeight;
  1048.             minimizeButton.textContent = '-';
  1049.         } else {
  1050.             content.style.display = 'none';
  1051.             hud.style.height = headerHeight;
  1052.             minimizeButton.textContent = '+';
  1053.         }
  1054.     });
  1055.     
  1056.     // Initial theme application
  1057.     applyHUDTheme();
  1058.     
  1059.     return hud;
  1060. }
  1061.  
  1062. // ----------------------------------------------------
  1063. // Main Loop Update Function (ut)
  1064. // ----------------------------------------------------
  1065. function ut() {
  1066.   try {
  1067.     if (typeof w === "undefined" || !w || !w.cursors) {
  1068.       if (!hudElement) createDraggableHUD([]);
  1069.       const userList = document.getElementById('userListContent');
  1070.       if (userList) userList.textContent = "Waiting for 'w' (game object) to be available.";
  1071.       requestAnimationFrame(ut);
  1072.       return;
  1073.     }
  1074.  
  1075.     const a = Array.from(w.cursors);
  1076.     let out = "";
  1077.     const usernamesRaw = [];
  1078.  
  1079.     for (let i = 0; i < a.length; i++) {
  1080.       const cur = a[i][1];
  1081.       const nameHTML = `<span style="color:${cur.c}">${cur.n}</span>`;
  1082.       usernamesRaw.push(cur.n);
  1083.       
  1084.       const coords = cur.l && cur.l.length === 2 ? `(${cur.l[0]}, ${cur.l[1]})` : 'N/A';
  1085.       const colorId = cur.c || 'none';
  1086.  
  1087.       out += `${nameHTML} ~ ${cur.id} | ${coords} | Color: ${colorId}\n`;
  1088.     }
  1089.  
  1090.     out += "\nClient ID: " + (w.clientId || "unknown");
  1091.     
  1092.     // --- HUD Management ---
  1093.     const hud = hudElement || createDraggableHUD(usernamesRaw);
  1094.     const selectEl = document.getElementById("userJumpSelect");
  1095.     const userListEl = document.getElementById('userListContent');
  1096.  
  1097.     // Update Dropdown
  1098.     if (selectEl) {
  1099.       const selectedValue = selectEl.value;
  1100.  
  1101.       selectEl.innerHTML = `<option value="">Teleport / Follow user…</option>`;
  1102.       usernamesRaw.forEach((name, i) => {
  1103.         const opt = el("option", { value: String(i), text: name });
  1104.         selectEl.appendChild(opt);
  1105.       });
  1106.       
  1107.       if (selectedValue !== "") {
  1108.           selectEl.value = selectedValue;
  1109.       } else if (followIndex >= 0 && followIndex < usernamesRaw.length) {
  1110.           selectEl.value = String(followIndex);
  1111.       }
  1112.     }
  1113.  
  1114.     // Update User List Content
  1115.     if (userListEl) userListEl.innerHTML = out;
  1116.  
  1117.     // TELEPORT / FOLLOW Logic
  1118.     if (utid >= 0 && utid < a.length) {
  1119.       const cur = a[utid][1];
  1120.       if (cur && cur.l && cur.l.length === 2 && typeof w.tp === "function") {
  1121.         w.tp(cur.l[0], cur.l[1]);
  1122.       }
  1123.       utid = -1;
  1124.     }
  1125.     if (followMode && followIndex >= 0 && followIndex < a.length) {
  1126.       const cur = a[followIndex][1];
  1127.       if (cur && cur.l && cur.l.length === 2 && typeof w.tp === "function") {
  1128.         w.tp(cur.l[0], cur.l[1]);
  1129.       }
  1130.     }
  1131.   } catch (err) {
  1132.     console.error("HUD loop error:", err);
  1133.     const userList = document.getElementById('userListContent');
  1134.     if (userList) userList.textContent = "HUD Error: " + err.message;
  1135.   }
  1136.  
  1137.   requestAnimationFrame(ut);
  1138. }
  1139.  
  1140. // ----------------------------------------------------
  1141. // ⭐ INITIALIZATION
  1142. // ----------------------------------------------------
  1143. loadCustomThemes(); 
  1144. ut();
Advertisement
Comments
Add Comment
Please, Sign In to add comment