From e4bf947531b8a8520789a3a549509da76b5da641 Mon Sep 17 00:00:00 2001 From: Mizzajl Date: Fri, 18 Oct 2024 19:28:03 +0200 Subject: [PATCH] css/html CrimeMonitor --- Mizzajl/home/CrimeMonitor.js | 14 +- Mizzajl/home/test.js | 238 +++++++++++++++++++++++++ Mizzajl/home/test2.js | 286 ++++++++++++++++++++++++++++++ Mizzajl/home/test3.js | 330 +++++++++++++++++++++++++++++++++++ 4 files changed, 862 insertions(+), 6 deletions(-) create mode 100644 Mizzajl/home/test.js create mode 100644 Mizzajl/home/test2.js create mode 100644 Mizzajl/home/test3.js diff --git a/Mizzajl/home/CrimeMonitor.js b/Mizzajl/home/CrimeMonitor.js index 099f45d..d3c502b 100644 --- a/Mizzajl/home/CrimeMonitor.js +++ b/Mizzajl/home/CrimeMonitor.js @@ -3,7 +3,7 @@ import { GetCrimeStat } from "./Library"; /** @param {NS} ns */ export async function main(ns) { ns.tail(); - ns.resizeTail(1876, 412); + ns.resizeTail(2005, 412); ns.disableLog("sleep"); let aCrimes = [ @@ -26,9 +26,9 @@ export async function main(ns) { ns.clearLog(); // Header with ASCII box - ns.printRaw("╔══════════════════╦════════════╦═════════╦════════════════════╦════════════════════╦════════════════════╦════════════════════╦════════════════════╦════════════════════╦════════════╦════════════╗"); - ns.printRaw("║ Crime ║ Chance ║ $/%/s ║ Hack w/xp ║ Str w/xp ║ Def w/xp ║ Dex w/xp ║ Agi w/xp ║ Cha w/xp ║ Money/s ║ Karma/s ║"); - ns.printRaw("╠══════════════════╬════════════╬═════════╬════════════════════╬════════════════════╬════════════════════╬════════════════════╬════════════════════╬════════════════════╬════════════╬════════════╣"); + ns.printRaw("╔══════════════════╦════════════╦═════════╦════════════════════╦════════════════════╦════════════════════╦════════════════════╦════════════════════╦════════════════════╦════════════╦════════════╦════════════╗"); + ns.printRaw("║ Crime ║ Chance ║ $/%/s ║ Hack w/xp ║ Str w/xp ║ Def w/xp ║ Dex w/xp ║ Agi w/xp ║ Cha w/xp ║ IntXP /s ║ Money/s ║ Karma/s ║"); + ns.printRaw("╠══════════════════╬════════════╬═════════╬════════════════════╬════════════════════╬════════════════════╬════════════════════╬════════════════════╬════════════════════╬════════════╬════════════╬════════════╣"); // Loop through each crime and display stats for (let i = 0; i < aCrimes.length; i++) { @@ -42,6 +42,7 @@ export async function main(ns) { let dexterityExpPerSecond = await GetCrimeStat(ns, crime, "dexterity_exp") / (await GetCrimeStat(ns, crime, "time") / 1000); let agilityExpPerSecond = await GetCrimeStat(ns, crime, "agility_exp") / (await GetCrimeStat(ns, crime, "time") / 1000); let charismaExpPerSecond = await GetCrimeStat(ns, crime, "charisma_exp") / (await GetCrimeStat(ns, crime, "time") / 1000); + let IntelligenceExpPerSecond = await GetCrimeStat(ns, crime, "intelligence_exp") / (await GetCrimeStat(ns, crime, "time") / 1000); // Success weights for each attribute let hackingSuccessWeight = await GetCrimeStat(ns, crime, "hacking_success_weight"); @@ -70,15 +71,16 @@ export async function main(ns) { let dexterityDisplay = `${dexteritySuccessWeight.toFixed(2)} / ${dexterityExpPerSecond.toFixed(4)}`.padEnd(19, " "); let agilityDisplay = `${agilitySuccessWeight.toFixed(2)} / ${agilityExpPerSecond.toFixed(4)}`.padEnd(19, " "); let charismaDisplay = `${charismaSuccessWeight.toFixed(2)} / ${charismaExpPerSecond.toFixed(4)}`.padEnd(19, " "); + let IntelligenceDisplay = `${IntelligenceExpPerSecond.toFixed(4)}`.padEnd(11, " "); let moneyDisplay = moneyPerSecond.toFixed(2).padEnd(11, " "); let karmaDisplay = karmaPerSecond.toFixed(4).padEnd(11, " "); - ns.printRaw(`║ ${crimeDisplay}║ ${chanceDisplay}║ ${nMoneyPerChancePerSecondDisplay}║ ${hackingDisplay}║ ${strengthDisplay}║ ${defenseDisplay}║ ${dexterityDisplay}║ ${agilityDisplay}║ ${charismaDisplay}║ ${moneyDisplay}║ ${karmaDisplay}║`); + ns.printRaw(`║ ${crimeDisplay}║ ${chanceDisplay}║ ${nMoneyPerChancePerSecondDisplay}║ ${hackingDisplay}║ ${strengthDisplay}║ ${defenseDisplay}║ ${dexterityDisplay}║ ${agilityDisplay}║ ${charismaDisplay}║ ${IntelligenceDisplay}║ ${moneyDisplay}║ ${karmaDisplay}║`); } // Footer with ASCII box - ns.printRaw("╚══════════════════╩════════════╩═════════╩════════════════════╩════════════════════╩════════════════════╩════════════════════╩════════════════════╩════════════════════╩════════════╩════════════╝"); + ns.printRaw("╚══════════════════╩════════════╩═════════╩════════════════════╩════════════════════╩════════════════════╩════════════════════╩════════════════════╩════════════════════╩════════════╩════════════╩════════════╝"); // Wait for 1 second before updating the display again await ns.sleep(1000); diff --git a/Mizzajl/home/test.js b/Mizzajl/home/test.js new file mode 100644 index 0000000..2e80a0b --- /dev/null +++ b/Mizzajl/home/test.js @@ -0,0 +1,238 @@ +/** @param {NS} ns */ +export async function main(ns) { + ns.disableLog('sleep'); // Disable log spamming + + let counter = 0; + let incrementValue = 1; + let isRunning = true; + let isMinimized = false; // Tracks whether the window is minimized or not + + // Define colors from the provided theme + const theme = { + primary: "#0c0", + primarylight: "#0f0", + primarydark: "#090", + error: "#c00", + backgroundprimary: "#000", // Black background + button: "#333", // Button background + buttonHoverLight: "#555", // Lighter button hover effect + buttonHoverDark: "#111", // Darker button hover effect + rippleColor: "rgba(255, 255, 255, 0.5)", // Color of the ripple effect + border: "#666" // Border color + }; + + // Create a div for the display (the main window) + const doc = eval("document"); + let container = doc.createElement("div"); + container.style = ` + position: fixed; + top: 100px; + left: 100px; + background: ${theme.backgroundprimary}; /* Solid black background */ + color: ${theme.primary}; /* Primary color for text */ + padding: 5px; + z-index: 1000; + font-family: monospace; + border: 2px solid ${theme.border}; /* Border around the whole window */ + width: 220px; + border-radius: 5px; + `; + doc.body.appendChild(container); + + // Create title bar with ^ and X buttons (minimize and close) + let titleBar = doc.createElement("div"); + titleBar.style = ` + display: flex; + justify-content: space-between; + align-items: center; + font-weight: bold; + color: ${theme.primarylight}; /* Light primary color for header */ + cursor: default; + padding-bottom: 5px; + border-bottom: 1px solid ${theme.border}; /* Border between title and content */ + `; + container.appendChild(titleBar); + + let title = doc.createElement("span"); + title.innerText = "Overview Counter"; + titleBar.appendChild(title); + + let controls = doc.createElement("div"); + + // Create minimize button (^ for minimize/restore) + let minimizeButton = doc.createElement("button"); + minimizeButton.innerText = "^"; + minimizeButton.style = ` + background: none; + border: none; + color: ${theme.primarylight}; /* Light primary color for minimize button */ + cursor: pointer; + font-weight: bold; + padding: 0 5px; + `; + controls.appendChild(minimizeButton); + + // Create close button (X to close) + let closeButton = doc.createElement("button"); + closeButton.innerText = "X"; + closeButton.style = ` + background: none; + border: none; + color: ${theme.error}; /* Error color for close button */ + cursor: pointer; + font-weight: bold; + padding: 0 5px; + `; + controls.appendChild(closeButton); + + titleBar.appendChild(controls); + + // Create the content area (display for counter and buttons) + let content = doc.createElement("div"); + content.style = "margin-top: 10px;"; + container.appendChild(content); + + let counterDisplay = doc.createElement("p"); + counterDisplay.style = ` + margin: 5px 0; + color: ${theme.primarylight}; /* Primary light color for counter text */ + `; + content.appendChild(counterDisplay); + + // Function to apply button styles with hover and ripple effects + function createStyledButton(text) { + let button = doc.createElement("button"); + button.innerText = text; + button.style = ` + position: relative; + overflow: hidden; /* Allow ripple effect to be confined inside the button */ + background: ${theme.button}; /* Button background color */ + color: ${theme.primary}; /* Primary color for button text */ + border: 1px solid ${theme.border}; + cursor: pointer; + margin-right: 5px; + margin-bottom: 5px; + padding: 5px; + width: 100%; + border-radius: 3px; + transition: background-color 0.3s ease; /* Smooth transition for hover effect */ + `; + + // Apply hover effect (lighter if button is dark, darker if light) + button.addEventListener("mouseenter", () => { + button.style.backgroundColor = theme.button === "#333" ? theme.buttonHoverLight : theme.buttonHoverDark; + }); + + button.addEventListener("mouseleave", () => { + button.style.backgroundColor = theme.button; + }); + + // Add ripple effect on click + button.addEventListener("click", (e) => { + let ripple = doc.createElement("span"); + let size = Math.max(button.offsetWidth, button.offsetHeight); + let x = e.clientX - button.getBoundingClientRect().left - size / 2; + let y = e.clientY - button.getBoundingClientRect().top - size / 2; + + ripple.style = ` + position: absolute; + width: ${size}px; + height: ${size}px; + background: ${theme.rippleColor}; + border-radius: 50%; + opacity: 0; + animation: ripple-animation 0.6s ease-out; + left: ${x}px; + top: ${y}px; + `; + button.appendChild(ripple); + + // Remove ripple element after animation + ripple.addEventListener("animationend", () => { + ripple.remove(); + }); + }); + + return button; + } + + // Add keyframes for ripple animation + const styleSheet = doc.styleSheets[0]; + styleSheet.insertRule(` + @keyframes ripple-animation { + from { + transform: scale(0); + opacity: 0.6; + } + to { + transform: scale(4); + opacity: 0; + } + } + `, styleSheet.cssRules.length); + + // Create buttons with hover effects, animations, and ripple effect + let incrementButton = createStyledButton("Increase Increment"); + let decrementButton = createStyledButton("Decrease Increment"); + let pauseButton = createStyledButton("Pause/Resume"); + let quitButton = createStyledButton("Quit"); + + content.appendChild(incrementButton); + content.appendChild(decrementButton); + content.appendChild(pauseButton); + content.appendChild(quitButton); + + // Add event listeners for the buttons + incrementButton.addEventListener("click", () => incrementValue += 1); + decrementButton.addEventListener("click", () => incrementValue = Math.max(0, incrementValue - 1)); + pauseButton.addEventListener("click", () => incrementValue = incrementValue === 0 ? 1 : 0); + quitButton.addEventListener("click", () => { + isRunning = false; + container.remove(); // Remove the container on quit + }); + + // Minimize/Restore window + minimizeButton.addEventListener("click", () => { + isMinimized = !isMinimized; + content.style.display = isMinimized ? "none" : "block"; + }); + + // Close window + closeButton.addEventListener("click", () => { + isRunning = false; + container.remove(); // Remove the window + }); + + // Make the window draggable + let isDragging = false; + let offsetX, offsetY; + + container.addEventListener("mousedown", (e) => { + isDragging = true; + offsetX = e.clientX - container.getBoundingClientRect().left; + offsetY = e.clientY - container.getBoundingClientRect().top; + }); + + doc.addEventListener("mousemove", (e) => { + if (isDragging) { + container.style.left = `${e.clientX - offsetX}px`; + container.style.top = `${e.clientY - offsetY}px`; + } + }); + + doc.addEventListener("mouseup", () => { + isDragging = false; + }); + + // Main loop to update the counter display + while (isRunning) { + counter += incrementValue; + counterDisplay.innerText = `Counter: ${counter}\nIncrement Value: ${incrementValue}`; + + await ns.sleep(1000); // Sleep 1 second between updates + } + + // Remove the container when the script stops + container.remove(); + ns.tprint("Script terminated."); +} diff --git a/Mizzajl/home/test2.js b/Mizzajl/home/test2.js new file mode 100644 index 0000000..573fc4b --- /dev/null +++ b/Mizzajl/home/test2.js @@ -0,0 +1,286 @@ +/** @param {NS} ns */ +export async function main(ns) { + ns.disableLog('sleep'); // Disable log spamming + + const doc = eval("document"); + const body = doc.body; + let isMinimized = false; // Track the minimize state + + // Read crime stats from the CrimeStats.txt file + let crimeStatsData = await ns.read("CrimeStats.txt"); + let crimes = JSON.parse(crimeStatsData); // Parse the JSON data from the file + + // Add dynamic crime chance and calculate stats per second + for (let crime of crimes) { + crime.chance = ns.singularity.getCrimeChance(crime.name) * 100; // Get success chance as a number + let timeInSeconds = crime.time / 1000; // Convert ms to seconds for calculations + + // Calculate stats per second + crime.hacking_exp_per_sec = crime.hacking_exp / timeInSeconds; + crime.strength_exp_per_sec = crime.strength_exp / timeInSeconds; + crime.defense_exp_per_sec = crime.defense_exp / timeInSeconds; + crime.dexterity_exp_per_sec = crime.dexterity_exp / timeInSeconds; + crime.agility_exp_per_sec = crime.agility_exp / timeInSeconds; + crime.charisma_exp_per_sec = crime.charisma_exp / timeInSeconds; + crime.intelligence_exp_per_sec = crime.intelligence_exp / timeInSeconds || 0; // Handle potential missing data + crime.money_per_sec = crime.money / timeInSeconds; + crime.karma_per_sec = crime.karma / timeInSeconds; + } + + // Create the CSS window container + let container = doc.createElement("div"); + container.style = ` + position: fixed; + top: 100px; + left: 100px; + background: black; /* Black background */ + color: #0c0; /* Green text */ + z-index: 1000; + font-family: "Source Code Pro", monospace; /* Apply the terminal font */ + font-size: 16px; + border: 2px solid #666; + border-radius: 5px; + width: 1400px; /* Increased width */ + height: 400px; /* Increased height */ + overflow-y: auto; + `; + body.appendChild(container); + + // Create the title bar with minimize and close buttons + let titleBar = doc.createElement("div"); + titleBar.style = ` + display: flex; + justify-content: space-between; + align-items: center; + background: black; /* Black background for title bar */ + color: #0c0; /* Green text */ + border-bottom: 1px solid #666; + font-weight: bold; + `; + container.appendChild(titleBar); + + // Title text + let title = doc.createElement("span"); + title.innerText = "Crime Stats Monitor"; + titleBar.appendChild(title); + + // Create a container for the buttons and align them to the right + let buttonContainer = doc.createElement("div"); + buttonContainer.style = ` + display: flex; + justify-content: flex-end; + `; + titleBar.appendChild(buttonContainer); + + // Minimize button (▲ for minimize, ▼ for restore) + let minimizeButton = doc.createElement("button"); + minimizeButton.innerText = "▲"; // Start as minimize button + minimizeButton.style = ` + background: none; + border: 1px solid #666; + color: #0c0; /* Green text */ + cursor: pointer; + font-weight: bold; + padding: 5px; + width: 30px; /* Set same width for buttons */ + `; + buttonContainer.appendChild(minimizeButton); + + minimizeButton.addEventListener("click", () => { + if (isMinimized) { + minimizeButton.innerText = "▲"; // Minimize arrow + container.style.height = "400px"; // Restore height + } else { + minimizeButton.innerText = "▼"; // Restore arrow + container.style.height = "25px"; // Minimize height + } + isMinimized = !isMinimized; + }); + + // Close button (X) + let closeButton = doc.createElement("button"); + closeButton.innerText = "X"; + closeButton.style = ` + background: none; + border: 1px solid #666; + color: #0c0; /* Green text */ + cursor: pointer; + font-weight: bold; + padding: 5px; + width: 30px; /* Set same width for buttons */ + `; + buttonContainer.appendChild(closeButton); + + closeButton.addEventListener("click", () => { + container.remove(); // Close the window + }); + + // Table for displaying the crime stats + let table = doc.createElement("table"); + table.style = ` + width: 100%; + border-collapse: collapse; + color: #0c0; /* Green text */ + `; + container.appendChild(table); + + // Track the current sort direction for each column (null, 'asc', or 'desc') + let sortOrder = new Array(13).fill(null); // One for each column + + // Add table headers with clickable sorting functionality and arrows + let headers = [ + "Crime", "Time (s)", "Chance", "Hack Exp/s", "Str Exp/s", "Def Exp/s", + "Dex Exp/s", "Agi Exp/s", "Cha Exp/s", "Money/s", "Karma/s", "$/%/s", "IntXP/s" + ]; + + let headerRow = doc.createElement("tr"); + + headers.forEach((header, index) => { + let th = doc.createElement("th"); + th.innerHTML = `${header} `; // Span for the arrow + th.style = ` + padding: 5px; + background: #333; + border: 1px solid #666; + cursor: pointer; + white-space: nowrap; /* Prevent wrapping */ + `; + + // Add sorting functionality for each column + th.addEventListener("click", () => { + clearAllArrows(); // Remove all arrows + sortCrimesByColumn(index); // Sort by this column + toggleSortOrder(index); // Toggle sort order for this column + }); + headerRow.appendChild(th); + }); + + table.appendChild(headerRow); + + // Function to toggle the sort order for a column + function toggleSortOrder(columnIndex) { + // Toggle between 'asc' and 'desc' + if (sortOrder[columnIndex] === 'asc') { + sortOrder[columnIndex] = 'desc'; + } else { + sortOrder[columnIndex] = 'asc'; + } + + // Update the arrows for this column + updateArrow(columnIndex); + } + + // Function to update the arrow for a specific column + function updateArrow(columnIndex) { + let th = headerRow.querySelectorAll("th")[columnIndex]; + let arrowSpan = th.querySelector("span"); + + if (sortOrder[columnIndex] === 'asc') { + arrowSpan.innerHTML = " ▲"; // Up arrow for ascending + } else if (sortOrder[columnIndex] === 'desc') { + arrowSpan.innerHTML = " ▼"; // Down arrow for descending + } + } + + // Function to clear all arrows + function clearAllArrows() { + headerRow.querySelectorAll("th span").forEach(arrow => { + arrow.innerHTML = ""; // Remove all arrows + }); + } + + // Function to format numbers with 4 decimal places + function formatNumber(num) { + return parseFloat(num.toFixed(4)); // Convert to number with 4 decimal places + } + + // Function to display crimes in the table + function displayCrimes(crimesList) { + // Clear any existing rows (except the header) + while (table.rows.length > 1) { + table.deleteRow(1); + } + + crimesList.forEach(crime => { + let row = doc.createElement("tr"); + + let columns = [ + { text: crime.name, value: crime.name }, // Treat as string + { text: formatNumber(crime.time / 1000), value: crime.time / 1000 }, // Time in seconds + { text: formatNumber(crime.chance), value: crime.chance }, // Success chance + { text: formatNumber(crime.hacking_exp_per_sec), value: crime.hacking_exp_per_sec }, + { text: formatNumber(crime.strength_exp_per_sec), value: crime.strength_exp_per_sec }, + { text: formatNumber(crime.defense_exp_per_sec), value: crime.defense_exp_per_sec }, + { text: formatNumber(crime.dexterity_exp_per_sec), value: crime.dexterity_exp_per_sec }, + { text: formatNumber(crime.agility_exp_per_sec), value: crime.agility_exp_per_sec }, + { text: formatNumber(crime.charisma_exp_per_sec), value: crime.charisma_exp_per_sec }, + { text: formatNumber(crime.money_per_sec), value: crime.money_per_sec }, + { text: formatNumber(crime.karma_per_sec), value: crime.karma_per_sec }, + { text: formatNumber(crime.money * crime.chance / 100), value: crime.money * crime.chance / 100 }, // Money per chance per second + { text: formatNumber(crime.intelligence_exp_per_sec), value: crime.intelligence_exp_per_sec } // Intelligence XP per second + ]; + + columns.forEach((col, index) => { + let td = doc.createElement("td"); + td.innerText = col.text; // Display formatted text + td.dataset.value = col.value; // Store the numeric value for sorting + + td.style = ` + padding: 5px; + border-bottom: 1px solid #666; + text-align: center; + white-space: nowrap; /* Prevent text wrapping */ + `; + row.appendChild(td); + }); + + table.appendChild(row); + }); + } + + // Sorting function (high to low for numeric values) + function sortCrimesByColumn(columnIndex) { + let rows = Array.from(table.querySelectorAll('tr:nth-child(n+2)')); // Get all rows except header + let order = sortOrder[columnIndex]; // Ascending or descending + + rows.sort((rowA, rowB) => { + let valA = rowA.cells[columnIndex].dataset.value; + let valB = rowB.cells[columnIndex].dataset.value; + + // Check if values are numeric and sort accordingly + if (!isNaN(valA) && !isNaN(valB)) { + return order === 'asc' ? parseFloat(valA) - parseFloat(valB) : parseFloat(valB) - parseFloat(valA); + } + + // If not numeric, sort alphabetically + return order === 'asc' ? String(valA).localeCompare(String(valB)) : String(valB).localeCompare(String(valA)); + }); + + // Append rows in sorted order + rows.forEach(row => table.appendChild(row)); + } + + // Display the crimes initially + displayCrimes(crimes); + + // Make the window draggable + let isDragging = false; + let offsetX, offsetY; + + titleBar.addEventListener("mousedown", (e) => { + isDragging = true; + offsetX = e.clientX - container.getBoundingClientRect().left; + offsetY = e.clientY - container.getBoundingClientRect().top; + }); + + doc.addEventListener("mousemove", (e) => { + if (isDragging) { + container.style.left = `${e.clientX - offsetX}px`; + container.style.top = `${e.clientY - offsetY}px`; + } + }); + + doc.addEventListener("mouseup", () => { + isDragging = false; + }); +} diff --git a/Mizzajl/home/test3.js b/Mizzajl/home/test3.js new file mode 100644 index 0000000..1cbae3b --- /dev/null +++ b/Mizzajl/home/test3.js @@ -0,0 +1,330 @@ +/** @param {NS} ns */ +export async function main(ns) { + ns.disableLog('sleep'); // Disable log spamming + + const doc = eval("document"); + const body = doc.body; + let isMinimized = false; // Track the minimize state + + // Read crime stats from the CrimeStats.txt file + let crimeStatsData = await ns.read("CrimeStats.txt"); + let crimes = JSON.parse(crimeStatsData); // Parse the JSON data from the file + + // Add dynamic crime chance and calculate stats per second + for (let crime of crimes) { + crime.chance = ns.singularity.getCrimeChance(crime.name) * 100; // Get success chance as a number + let timeInSeconds = crime.time / 1000; // Convert ms to seconds for calculations + + // Calculate stats per second + crime.hacking_exp_per_sec = crime.hacking_exp / timeInSeconds; + crime.strength_exp_per_sec = crime.strength_exp / timeInSeconds; + crime.defense_exp_per_sec = crime.defense_exp / timeInSeconds; + crime.dexterity_exp_per_sec = crime.dexterity_exp / timeInSeconds; + crime.agility_exp_per_sec = crime.agility_exp / timeInSeconds; + crime.charisma_exp_per_sec = crime.charisma_exp / timeInSeconds; + crime.intelligence_exp_per_sec = crime.intelligence_exp / timeInSeconds || 0; // Handle potential missing data + crime.money_per_sec = crime.money / timeInSeconds; + crime.karma_per_sec = crime.karma / timeInSeconds; + crime.money_per_chance = crime.money * (crime.chance / 100) / timeInSeconds; // Money per chance per second + } + + // Function to map value to a color between green and red + function getGradientColor(value, min, max, isReversed = false) { + if (min === max) return "rgb(0,255,0)"; // Return green if all values are the same + let normalized = (value - min) / (max - min); + if (isReversed) normalized = 1 - normalized; // Reverse the color gradient for columns where lower values are better + let r = Math.round(255 * (1 - normalized)); // Red for lower values + let g = Math.round(255 * normalized); // Green for higher values + return `rgb(${r},${g},0)`; // Return color from green to red + } + + // Create the CSS window container + let container = doc.createElement("div"); + container.style = ` + position: fixed; + top: 100px; + left: 100px; + background: black; /* Black background */ + color: #0c0; /* Green text */ + z-index: 1000; + font-family: "Source Code Pro", monospace; /* Apply the terminal font */ + font-size: 16px; + border: 2px solid #666; + border-radius: 5px; + width: 1400px; /* Increased width */ + height: 400px; /* Increased height */ + overflow-y: auto; + `; + body.appendChild(container); + + // Create the title bar with minimize and close buttons + let titleBar = doc.createElement("div"); + titleBar.style = ` + display: flex; + justify-content: space-between; + align-items: center; + background: black; /* Black background for title bar */ + color: #0c0; /* Green text */ + border-bottom: 1px solid #666; + font-weight: bold; + `; + container.appendChild(titleBar); + + // Title text + let title = doc.createElement("span"); + title.innerText = "Crime Stats Monitor"; + titleBar.appendChild(title); + + // Create a container for the buttons and align them to the right + let buttonContainer = doc.createElement("div"); + buttonContainer.style = ` + display: flex; + justify-content: flex-end; + `; + titleBar.appendChild(buttonContainer); + + // Minimize button (▲ for minimize, ▼ for restore) + let minimizeButton = doc.createElement("button"); + minimizeButton.innerText = "▲"; // Start as minimize button + minimizeButton.style = ` + background: none; + border: 1px solid #666; + color: #0c0; /* Green text */ + cursor: pointer; + font-weight: bold; + padding: 5px; + width: 30px; /* Set same width for buttons */ + `; + buttonContainer.appendChild(minimizeButton); + + minimizeButton.addEventListener("click", () => { + if (isMinimized) { + minimizeButton.innerText = "▲"; // Minimize arrow + container.style.height = "400px"; // Restore height + } else { + minimizeButton.innerText = "▼"; // Restore arrow + container.style.height = "25px"; // Minimize height + } + isMinimized = !isMinimized; + }); + + // Close button (X) + let closeButton = doc.createElement("button"); + closeButton.innerText = "X"; + closeButton.style = ` + background: none; + border: 1px solid #666; + color: #0c0; /* Green text */ + cursor: pointer; + font-weight: bold; + padding: 5px; + width: 30px; /* Set same width for buttons */ + `; + buttonContainer.appendChild(closeButton); + + closeButton.addEventListener("click", () => { + container.remove(); // Close the window + }); + + // Table for displaying the crime stats + let table = doc.createElement("table"); + table.style = ` + width: 100%; + border-collapse: collapse; + color: #0c0; /* Green text */ + `; + container.appendChild(table); + + // Track the current sort direction for each column (null, 'asc', or 'desc') + let sortOrder = new Array(13).fill(null); // One for each column + + // Add table headers with clickable sorting functionality and arrows + let headers = [ + "Crime", "Time (s)", "Chance", "Hack Exp/s", "Str Exp/s", "Def Exp/s", + "Dex Exp/s", "Agi Exp/s", "Cha Exp/s", "Money/s", "Karma/s", "$/%/s", "IntXP/s" + ]; + + let headerRow = doc.createElement("tr"); + + headers.forEach((header, index) => { + let th = doc.createElement("th"); + th.innerHTML = `${header} `; // Span for the arrow + th.style = ` + padding: 5px; + background: #333; + border: 1px solid #666; + cursor: pointer; + white-space: nowrap; /* Prevent wrapping */ + `; + + // Add sorting functionality for each column + th.addEventListener("click", () => { + clearAllArrows(); // Remove all arrows + sortCrimesByColumn(index); // Sort by this column + toggleSortOrder(index); // Toggle sort order for this column + }); + headerRow.appendChild(th); + }); + + table.appendChild(headerRow); + + // Function to toggle the sort order for a column + function toggleSortOrder(columnIndex) { + // Toggle between 'asc' and 'desc' + if (sortOrder[columnIndex] === 'asc') { + sortOrder[columnIndex] = 'desc'; + } else { + sortOrder[columnIndex] = 'asc'; + } + + // Update the arrows for this column + updateArrow(columnIndex); + } + + // Function to update the arrow for a specific column + function updateArrow(columnIndex) { + let th = headerRow.querySelectorAll("th")[columnIndex]; + let arrowSpan = th.querySelector("span"); + + if (sortOrder[columnIndex] === 'asc') { + arrowSpan.innerHTML = " ▲"; // Up arrow for ascending + } else if (sortOrder[columnIndex] === 'desc') { + arrowSpan.innerHTML = " ▼"; // Down arrow for descending + } + } + + // Function to clear all arrows + function clearAllArrows() { + headerRow.querySelectorAll("th span").forEach(arrow => { + arrow.innerHTML = ""; // Remove all arrows + }); + } + + // Function to format numbers with 4 decimal places + function formatNumber(num) { + return parseFloat(num.toFixed(4)); // Convert to number with 4 decimal places + } + + // Function to display crimes in the table + function displayCrimes(crimesList) { + // Clear any existing rows (except the header) + while (table.rows.length > 1) { + table.deleteRow(1); + } + + // Find min and max for each column (for color gradient) + const columnsWithValues = [ + { key: "time", reverseGradient: true }, + { key: "chance", reverseGradient: false }, + { key: "hacking_exp_per_sec", reverseGradient: false }, + { key: "strength_exp_per_sec", reverseGradient: false }, + { key: "defense_exp_per_sec", reverseGradient: false }, + { key: "dexterity_exp_per_sec", reverseGradient: false }, + { key: "agility_exp_per_sec", reverseGradient: false }, + { key: "charisma_exp_per_sec", reverseGradient: false }, + { key: "money_per_sec", reverseGradient: false }, + { key: "karma_per_sec", reverseGradient: false }, + { key: "money_per_chance", reverseGradient: false }, + { key: "intelligence_exp_per_sec", reverseGradient: false } + ]; + + let minMax = {}; + + // Get min and max values for each column, excluding non-data elements + columnsWithValues.forEach(column => { + let values = crimesList.map(crime => crime[column.key]).filter(v => !isNaN(v)); // Ensure we're working with numbers + minMax[column.key] = { + min: Math.min(...values), + max: Math.max(...values) + }; + }); + + crimesList.forEach(crime => { + let row = doc.createElement("tr"); + + let columns = [ + { text: crime.name, value: crime.name, isName: true }, // Crime name (always green) + { text: formatNumber(crime.time / 1000), value: crime.time / 1000, key: "time" }, // Time in seconds + { text: formatNumber(crime.chance), value: crime.chance, key: "chance" }, // Success chance + { text: formatNumber(crime.hacking_exp_per_sec), value: crime.hacking_exp_per_sec, key: "hacking_exp_per_sec" }, + { text: formatNumber(crime.strength_exp_per_sec), value: crime.strength_exp_per_sec, key: "strength_exp_per_sec" }, + { text: formatNumber(crime.defense_exp_per_sec), value: crime.defense_exp_per_sec, key: "defense_exp_per_sec" }, + { text: formatNumber(crime.dexterity_exp_per_sec), value: crime.dexterity_exp_per_sec, key: "dexterity_exp_per_sec" }, + { text: formatNumber(crime.agility_exp_per_sec), value: crime.agility_exp_per_sec, key: "agility_exp_per_sec" }, + { text: formatNumber(crime.charisma_exp_per_sec), value: crime.charisma_exp_per_sec, key: "charisma_exp_per_sec" }, + { text: formatNumber(crime.money_per_sec), value: crime.money_per_sec, key: "money_per_sec" }, + { text: formatNumber(crime.karma_per_sec), value: crime.karma_per_sec, key: "karma_per_sec" }, + { text: formatNumber(crime.money_per_chance), value: crime.money_per_chance, key: "money_per_chance" }, // Money per chance per second + { text: formatNumber(crime.intelligence_exp_per_sec), value: crime.intelligence_exp_per_sec, key: "intelligence_exp_per_sec" } // Intelligence XP per second + ]; + + columns.forEach((col, index) => { + let td = doc.createElement("td"); + td.innerText = col.text; // Display formatted text + td.dataset.value = col.value; // Store the numeric value for sorting + + // Apply the color gradient to each cell based on the column + let gradientColor = col.isName + ? "#0c0" // Always green for names + : getGradientColor(col.value, minMax[col.key].min, minMax[col.key].max, columnsWithValues[index]?.reverseGradient); + + td.style.color = gradientColor; // Apply color + + td.style.padding = "5px"; + td.style.borderBottom = "1px solid #666"; + td.style.textAlign = "center"; + td.style.whiteSpace = "nowrap"; // Prevent text wrapping + + row.appendChild(td); + }); + + table.appendChild(row); + }); + } + + // Sorting function (high to low for numeric values) + function sortCrimesByColumn(columnIndex) { + let rows = Array.from(table.querySelectorAll('tr:nth-child(n+2)')); // Get all rows except header + let order = sortOrder[columnIndex]; // Ascending or descending + + rows.sort((rowA, rowB) => { + let valA = rowA.cells[columnIndex].dataset.value; + let valB = rowB.cells[columnIndex].dataset.value; + + // Check if values are numeric and sort accordingly + if (!isNaN(valA) && !isNaN(valB)) { + return order === 'asc' ? parseFloat(valA) - parseFloat(valB) : parseFloat(valB) - parseFloat(valA); + } + + // If not numeric, sort alphabetically + return order === 'asc' ? String(valA).localeCompare(String(valB)) : String(valB).localeCompare(String(valA)); + }); + + // Append rows in sorted order + rows.forEach(row => table.appendChild(row)); + } + + // Display the crimes initially + displayCrimes(crimes); + + // Make the window draggable + let isDragging = false; + let offsetX, offsetY; + + titleBar.addEventListener("mousedown", (e) => { + isDragging = true; + offsetX = e.clientX - container.getBoundingClientRect().left; + offsetY = e.clientY - container.getBoundingClientRect().top; + }); + + doc.addEventListener("mousemove", (e) => { + if (isDragging) { + container.style.left = `${e.clientX - offsetX}px`; + container.style.top = `${e.clientY - offsetY}px`; + } + }); + + doc.addEventListener("mouseup", () => { + isDragging = false; + }); +}