sleepy time update
This commit is contained in:
331
Mizzajl/home/UICrimeStats.js
Normal file
331
Mizzajl/home/UICrimeStats.js
Normal file
@@ -0,0 +1,331 @@
|
||||
/** @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 */
|
||||
opacity: 0.9; /* 50% transparency */
|
||||
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></span>`; // 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;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user