Last active
September 7, 2025 10:23
-
-
Save RadicalZephyr/8b799935b7c04290a13f92c359bcdabc to your computer and use it in GitHub Desktop.
My custom React Hooks for creating Bitburner React Components
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Create a FUID (fairly unique identifier) | |
| * | |
| * FUIDs have 3 components, the pid of the current process, the time | |
| * in milliseconds since the epoch start (Jan. 1st, 1970), and a 6 | |
| * digit random number. | |
| * | |
| * @param {NS} ns - Netscript API instance | |
| * @returns {string} A string representation of the FUID | |
| */ | |
| export function makeFuid(ns) { | |
| const pid = ns.pid; | |
| const ts = Date.now(); | |
| const r = Math.floor(Math.random() * 1e6); | |
| return `${pid}-${ts}-${r}`; | |
| } | |
| /** | |
| * Get an updating state value derived from polling the given function. | |
| * | |
| * @template T | |
| * @param {number} interval - Milliseconds between polling `pollFn` | |
| * @param {() => T} pollFn - Function to poll state changes | |
| * @returns {T} Reactive state produced by `pollFn` | |
| */ | |
| export function usePoll(ns, interval, pollFn) { | |
| const [data, setData] = React.useState(pollFn()); | |
| React.useEffect(() => { | |
| let id; | |
| const clearInterval = () => { | |
| if (id != null) globalThis.clearInterval(id); | |
| id = null; | |
| }; | |
| id = globalThis.setInterval(() => { | |
| try { | |
| setData(pollFn()); | |
| } catch (err) { | |
| console.error(err); | |
| clearInterval(); | |
| } | |
| }, interval); | |
| const exitHandlerName = 'usePoll-' + makeFuid(ns); | |
| ns.atExit(clearInterval, exitHandlerName); | |
| return () => { | |
| ns.atExit(() => null, exitHandlerName); | |
| clearInterval(); | |
| }; | |
| }, [ns, interval, pollFn]); | |
| return data; | |
| } | |
| /** | |
| * Get an updating state value derived from polling the Netscript API | |
| * with an update function. | |
| * | |
| * @template T | |
| * @param {NS} ns - Netscript API instance | |
| * @param {number} interval - Milliseconds between polling `updateFn` | |
| * @param {(ns: NS) => T} updateFn - Function to poll state from Netscript APIs | |
| * @returns {T} Reactive state produced by `updateFn` | |
| */ | |
| export function useNsUpdate(ns, interval, updateFn) { | |
| const [data, setData] = React.useState(updateFn(ns)); | |
| React.useEffect(() => { | |
| let id; | |
| const clearInterval = () => { | |
| if (id != null) globalThis.clearInterval(id); | |
| id = null; | |
| }; | |
| id = globalThis.setInterval(() => { | |
| try { | |
| setData(updateFn(ns)); | |
| } catch (err) { | |
| console.error(err); | |
| clearInterval(); | |
| } | |
| }, interval); | |
| const exitHandlerName = 'useNsUpdate-' + makeFuid(ns); | |
| ns.atExit(clearInterval, exitHandlerName); | |
| return () => { | |
| ns.atExit(() => null, exitHandlerName); | |
| clearInterval(); | |
| }; | |
| }, [ns, interval, updateFn]); | |
| return data; | |
| } | |
| /** | |
| * Keep a UserInterfaceTheme updated by polling `ns.ui.getTheme()`. | |
| * | |
| * @param {NS} ns - Netscript API instance | |
| * @param {number} interval - Milliseconds between theme refreshes | |
| * @returns {UserInterfaceTheme} The current theme from the UI | |
| */ | |
| export function useTheme(ns, interval = 200) { | |
| return useNsUpdate(ns, interval, getTheme); | |
| } | |
| /** | |
| * Return the current UI theme. | |
| * | |
| * @param {NS} ns | |
| * @returns {UserInterfaceTheme} | |
| */ | |
| function getTheme(ns) { | |
| return ns.ui.getTheme(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Create a FUID (fairly unique identifier) | |
| * | |
| * FUIDs have 3 components, the pid of the current process, the time | |
| * in milliseconds since the epoch start (Jan. 1st, 1970), and a 6 | |
| * digit random number. | |
| * | |
| * @param {NS} ns - Netscript API instance | |
| * @returns {string} A string representation of the FUID | |
| */ | |
| export function makeFuid(ns: NS): string { | |
| const pid = ns.pid; | |
| const ts = Date.now(); | |
| const r = Math.floor(Math.random() * 1e6); | |
| return `${pid}-${ts}-${r}`; | |
| } | |
| /** | |
| * Get an updating state value derived from polling the given function. | |
| * | |
| * @template T | |
| * @param {number} interval - Milliseconds between polling `pollFn` | |
| * @param {() => T} pollFn - Function to poll state changes | |
| * @returns {T} Reactive state produced by `pollFn` | |
| */ | |
| export function usePoll<T>(ns: NS, interval: number, pollFn: () => T): T { | |
| const [data, setData] = React.useState(pollFn()); | |
| React.useEffect(() => { | |
| let id: number | null; | |
| const clearInterval = () => { | |
| if (id != null) globalThis.clearInterval(id); | |
| id = null; | |
| }; | |
| id = globalThis.setInterval(() => { | |
| try { | |
| setData(pollFn()); | |
| } catch (err) { | |
| console.error(err); | |
| clearInterval(); | |
| } | |
| }, interval); | |
| const exitHandlerName = 'usePoll-' + makeFuid(ns); | |
| ns.atExit(clearInterval, exitHandlerName); | |
| return () => { | |
| ns.atExit(() => null, exitHandlerName); | |
| clearInterval(); | |
| }; | |
| }, [ns, interval, pollFn]); | |
| return data; | |
| } | |
| /** | |
| * Get an updating state value derived from polling the Netscript API | |
| * with an update function. | |
| * | |
| * @template T | |
| * @param {NS} ns - Netscript API instance | |
| * @param {number} interval - Milliseconds between polling `updateFn` | |
| * @param {(ns: NS) => T} updateFn - Function to poll state from Netscript APIs | |
| * @returns {T} Reactive state produced by `updateFn` | |
| */ | |
| export function useNsUpdate<T>( | |
| ns: NS, | |
| interval: number, | |
| updateFn: (ns: NS) => T, | |
| ): T { | |
| const [data, setData] = React.useState(updateFn(ns)); | |
| React.useEffect(() => { | |
| let id: number | null; | |
| const clearInterval = () => { | |
| if (id != null) globalThis.clearInterval(id); | |
| id = null; | |
| }; | |
| id = globalThis.setInterval(() => { | |
| try { | |
| setData(updateFn(ns)); | |
| } catch (err) { | |
| console.error(err); | |
| clearInterval(); | |
| } | |
| }, interval); | |
| const exitHandlerName = 'useNsUpdate-' + makeFuid(ns); | |
| ns.atExit(clearInterval, exitHandlerName); | |
| return () => { | |
| ns.atExit(() => null, exitHandlerName); | |
| clearInterval(); | |
| }; | |
| }, [ns, interval, updateFn]); | |
| return data; | |
| } | |
| /** | |
| * Keep a UserInterfaceTheme updated by polling `ns.ui.getTheme()`. | |
| * | |
| * @param {NS} ns - Netscript API instance | |
| * @param {number} interval - Milliseconds between theme refreshes | |
| * @returns {UserInterfaceTheme} The current theme from the UI | |
| */ | |
| export function useTheme(ns: NS, interval = 200): UserInterfaceTheme { | |
| return useNsUpdate(ns, interval, getTheme); | |
| } | |
| /** | |
| * Return the current UI theme. | |
| * | |
| * @param {NS} ns | |
| * @returns {UserInterfaceTheme} | |
| */ | |
| function getTheme(ns: NS): UserInterfaceTheme { | |
| return ns.ui.getTheme(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useNsUpdate } from 'util/hooks'; | |
| import { STATUS_WINDOW_WIDTH, STATUS_WINDOW_HEIGHT, KARMA_HEIGHT, } from 'util/ui'; | |
| export async function main(ns) { | |
| ns.disableLog('ALL'); | |
| ns.ui.openTail(); | |
| ns.ui.setTailTitle('Karma'); | |
| ns.ui.setTailFontSize(500); | |
| ns.ui.resizeTail(STATUS_WINDOW_WIDTH, KARMA_HEIGHT); | |
| const cellStyle = { | |
| padding: '0 0.5em', | |
| textAlign: 'left', | |
| }; | |
| ns.clearLog(); | |
| ns.printRaw(React.createElement(Karma, { ns: ns, cellStyle: cellStyle })); | |
| ns.ui.renderTail(); | |
| while (true) { | |
| const [ww] = ns.ui.windowSize(); | |
| ns.ui.moveTail(ww - STATUS_WINDOW_WIDTH, STATUS_WINDOW_HEIGHT); | |
| await ns.asleep(1000); | |
| } | |
| } | |
| function Karma({ ns, cellStyle }) { | |
| const karmaStats = useNsUpdate(ns, 100, getKarma); | |
| return (React.createElement(React.Fragment, null, | |
| React.createElement("table", null, | |
| React.createElement("tbody", null, | |
| React.createElement("tr", null, | |
| React.createElement("td", { style: cellStyle }, "Karma: "), | |
| React.createElement("td", { style: cellStyle }, ns.formatNumber(karmaStats.karma))), | |
| React.createElement("tr", null, | |
| React.createElement("td", { style: cellStyle }, "Victims: "), | |
| React.createElement("td", { style: cellStyle }, karmaStats.numKilled)))))); | |
| } | |
| function getKarma(ns) { | |
| const player = ns.getPlayer(); | |
| const karma = player.karma; | |
| const numKilled = player.numPeopleKilled; | |
| return { karma, numKilled }; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useNsUpdate } from 'hooks'; | |
| import { | |
| STATUS_WINDOW_WIDTH, | |
| STATUS_WINDOW_HEIGHT, | |
| KARMA_HEIGHT, | |
| } from 'util/ui'; | |
| export async function main(ns: NS) { | |
| ns.disableLog('ALL'); | |
| ns.ui.openTail(); | |
| ns.ui.setTailTitle('Karma'); | |
| ns.ui.setTailFontSize(500); | |
| ns.ui.resizeTail(STATUS_WINDOW_WIDTH, KARMA_HEIGHT); | |
| const cellStyle = { | |
| padding: '0 0.5em', | |
| textAlign: 'left', | |
| } as const; | |
| ns.clearLog(); | |
| ns.printRaw(<Karma ns={ns} cellStyle={cellStyle} />); | |
| ns.ui.renderTail(); | |
| while (true) { | |
| const [ww] = ns.ui.windowSize(); | |
| ns.ui.moveTail(ww - STATUS_WINDOW_WIDTH, STATUS_WINDOW_HEIGHT); | |
| await ns.asleep(1000); | |
| } | |
| } | |
| interface KarmaProps { | |
| ns: NS; | |
| cellStyle: object; | |
| } | |
| function Karma({ ns, cellStyle }: KarmaProps) { | |
| const karmaStats = useNsUpdate(ns, 100, getKarma); | |
| return ( | |
| <> | |
| <table> | |
| <tbody> | |
| <tr> | |
| <td style={cellStyle}>Karma: </td> | |
| <td style={cellStyle}> | |
| {ns.formatNumber(karmaStats.karma)} | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style={cellStyle}>Victims: </td> | |
| <td style={cellStyle}>{karmaStats.numKilled}</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </> | |
| ); | |
| } | |
| interface KarmaStats { | |
| karma: number; | |
| numKilled: number; | |
| } | |
| function getKarma(ns: NS): KarmaStats { | |
| const player = ns.getPlayer(); | |
| const karma = player.karma; | |
| const numKilled = player.numPeopleKilled; | |
| return { karma, numKilled }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment