bitburner-scripts/src/utils/utils.ts
Vomitblood f12de39c91 namespaced formulas for easier importing
Co-authored-by: Copilot <copilot@github.com>
2026-05-02 01:00:05 +08:00

155 lines
5.4 KiB
TypeScript

/**
* Format the date/time.
* If the date is today, show the time. Otherwise, show the date in "day month year" format.
* @param dateString The date string to format
* @returns Formatted date/time string
*/
export const utilityFormatDate = (dateString: string): string => {
const date = new Date(dateString);
const today = new Date();
// save today date at midnight for comparison
const todayMidnight = new Date(today.getFullYear(), today.getMonth(), today.getDate());
// check if the date is today
if (
date >= todayMidnight &&
date < new Date(todayMidnight.getTime() + 86400000 /* milliseconds in a day sheeesh */)
) {
// show time if today
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
} else {
// "day month year" format
return date.toLocaleDateString(undefined, { day: "numeric", month: "long", year: "numeric" });
}
};
/**
* Format bytes into human-readable size
* @param bytes Number of bytes to format
* @returns Formatted size string
*/
export const utilityFormatSize = (bytes: number): string => {
if (bytes === 0) return "0 Bytes";
const k = 1024;
const sizes = ["Bytes", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
// tf is pow
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
};
/**
* Convert a formatted size string back to bytes
* @param formattedSize Formatted size string (must be in the same format produced by `utilityFormatSize()`)
* @returns Number of bytes
*/
export const utilityUnformatSize = (formattedSize: string): number => {
const sizes = ["Bytes", "KB", "MB", "GB"];
const [value, unit] = formattedSize.split(" ");
const index = sizes.indexOf(unit);
return parseFloat(value) * Math.pow(1024, index);
};
/**
* Extract filename stem and extension from a file path
* @param filePath The file path to parse
* @returns Object containing `fileStem` and `fileExtension`. `fileStem` is the filename without the extension, and `fileExtension` is the extension in lowercase (or an empty string if there is no extension).
* @throws Error if the file path is invalid
*/
export const utilityExtractFilename = (filePath: string): { fileStem: string; fileExtension: string } | null => {
const lastSlashIndex = filePath.lastIndexOf("/");
const lastDotIndex = filePath.lastIndexOf(".");
// check if the last slash comes before the last dot
if (lastDotIndex !== -1 && lastSlashIndex > lastDotIndex) {
console.error("Invalid file path: the last slash comes before the last dot");
throw new Error("Invalid file path: the last slash comes before the last dot");
}
// check if the file path ends with a slash
if (lastSlashIndex === filePath.length - 1) {
console.error("Invalid file path: the file path ends with a slash");
throw new Error("Invalid file path: the file path ends with a slash");
}
let fileStem: string, fileExtension: string;
if (lastDotIndex === -1 || lastDotIndex < lastSlashIndex) {
// if there's no dot after the last slash, the whole name is the fileStem
fileStem = filePath.substring(lastSlashIndex + 1);
fileExtension = "";
} else {
fileStem = filePath.substring(lastSlashIndex + 1, lastDotIndex);
fileExtension = filePath.substring(lastDotIndex + 1).toLowerCase();
}
return { fileStem: fileStem, fileExtension: fileExtension };
};
/**
* Clamps a value to a lower and upper bound
* @param value Value to clamp
* @param min Lower bound, defaults to negative `Number.MAX_VALUE`
* @param max Upper bound, defaults to `Number.MAX_VALUE`
* @returns Clamped value
*/
export function clampNumber(value: number, min: number = -Number.MAX_VALUE, max: number = Number.MAX_VALUE): number {
if (isNaN(value)) {
// if (CONSTANTS.isDevBranch) throw new Error("NaN passed into clampNumber()");
return min;
}
return Math.max(Math.min(value, max), min);
}
/**
* Clamps a value to an integer within a lower and upper bound
* @param value Value to clamp
* @param min Lower bound, defaults to negative `Number.MAX_SAFE_INTEGER`
* @param max Upper bound, defaults to `Number.MAX_SAFE_INTEGER`
* @returns Clamped integer value
*/
export function clampInteger(
value: number,
min: number = -Number.MAX_SAFE_INTEGER,
max: number = Number.MAX_SAFE_INTEGER,
): number {
if (isNaN(value)) {
// if (CONSTANTS.isDevBranch) throw new Error("NaN passed into clampInteger()");
return min;
}
return Math.round(Math.max(Math.min(value, max), min));
}
/**
* Checks that a variable is a valid number
* A valid number must be of type "number" and cannot be NaN
* @param n The number to check
* @returns True if n is a valid number, false otherwise
*/
export function isValidNumber(n: number): boolean {
return typeof n === "number" && !isNaN(n);
}
/**
* Generate a random number between min and max (inclusive)
* @param min Minimum value (inclusive)
* @param max Maximum value (inclusive)
* @returns Random number between min and max
*/
export function randomNumber(min: number, max: number): number {
if (min > max) throw new Error("min cannot be greater than max");
return Math.random() * (max - min) + min;
}
/**
* Generate a random integer between min and max (inclusive)
* @param min Minimum value (inclusive)
* @param max Maximum value (inclusive)
* @returns Random integer between min and max
*/
export function randomInteger(min: number, max: number): number {
return Math.floor(randomNumber(min, max + 1));
}