Merge pull request #1562 from danielyxie/dev

Fix a few bugs
This commit is contained in:
hydroflame 2021-10-23 16:04:09 -04:00 committed by GitHub
commit ddb86ebfba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 871 additions and 467 deletions

@ -106,6 +106,21 @@ Fork and clone the repo
# Makes sure you always start from `danielyxie/dev` to avoid merge conflicts.
```
### Running locally.
Install
- `npm` (maybe via `nvm`)
- Github Desktop (windows only)
- Visual Studio code (optional)
Inside the root of the repo run
`npm install` to install all the dependencies
`npm run start:dev` to launch the game in dev mode.
After that you can open any browser and naviguate to `localhost:8000` and play the game.
Saving a file will reload the game automatically.
#### Submitting a Pull Request
When submitting a pull request with your code contributions, please abide by

64
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -0,0 +1,35 @@
Injecting HTML in the game
==========================
Bitburner uses React and Material-UI to render everything. Modifying the UI is possible but
not officially supported.
To automatically enter commands in the terminal (only works if looking at the terminal):
.. code-block:: javascript
// Acquire a reference to the terminal text field
const terminalInput = document.getElementById("terminal-input");
// Set the value to the command you want to run.
terminalInput.value="home;connect n00dles;home;connect n00dles;home;";
// Get a reference to the React event handler.
const handler = Object.keys(terminalInput)[1];
// Perform an onChange event to set some internal values.
terminalInput[handler].onChange({target:terminalInput});
// Simulate an enter press
terminalInput[handler].onKeyDown({keyCode:13,preventDefault:()=>null});
To add lines to the terminal (only works if looking at the terminal):
.. code-block:: javascript
// Acquire a reference to the terminal list of lines.
const list = document.getElementById("generic-react-container").querySelector("ul");
// Inject some HTML.
list.insertAdjacentHTML('beforeend',`<li><p color=lime>whatever custom html</p></li>`)

@ -11,3 +11,5 @@ they contain spoilers for the game.
getBitNodeMultipliers() <advancedfunctions/getBitNodeMultipliers>
getServer() <advancedfunctions/getServer>
autocomplete() <advancedfunctions/autocomplete>
atExit() <advancedfunctions/atExit>
Injecting HTML <advancedfunctions/inject_html.rst>

@ -17,7 +17,7 @@ getInformation() Netscript Function
maxHp: max hp of the sleeve,
jobs: jobs available to the sleeve,
jobTitle: job titles available to the sleeve,
tor: does this sleeve have access to the tor router,
mult: {
agility: agility multiplier,
agilityExp: agility exp multiplier,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -17,7 +17,6 @@
"@mui/material": "^5.0.3",
"@mui/styles": "^5.0.1",
"@types/escodegen": "^0.0.7",
"@types/js-beautify": "^1.13.2",
"@types/numeral": "0.0.25",
"@types/react": "^17.0.21",
"@types/react-dom": "^17.0.9",
@ -64,7 +63,6 @@
"html-webpack-plugin": "^3.2.0",
"http-server": "^13.0.1",
"jest": "^27.1.0",
"js-beautify": "^1.5.10",
"jsdom": "^15.0.0",
"lodash": "^4.17.21",
"mini-css-extract-plugin": "^0.4.1",

@ -206,7 +206,7 @@ export class Action implements IAction {
const city = inst.getCurrentCity();
if (city.chaos > BladeburnerConstants.ChaosThreshold) {
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);
const mult = Math.pow(diff, 0.1);
const mult = Math.pow(diff, 0.5);
return mult;
}

@ -1513,8 +1513,12 @@ export class Bladeburner implements IBladeburner {
if (this.logging.general) {
this.log(`Incited violence in the synthoid communities.`);
}
const city = this.cities[this.city];
city.chaos = (city.chaos + 100) * 2;
for (const cityName of Object.keys(this.cities)) {
const city = this.cities[cityName];
city.chaos += 10;
city.chaos += city.chaos / (Math.log(city.chaos) / Math.log(10));
}
this.startAction(player, this.action);
break;
}

@ -37,7 +37,7 @@ export class Operation extends Action {
const city = inst.getCurrentCity();
if (city.chaos > BladeburnerConstants.ChaosThreshold) {
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);
const mult = Math.pow(diff, 0.1);
const mult = Math.pow(diff, 0.5);
return mult;
}

@ -153,7 +153,7 @@ function WarehouseRoot(props: IProps): React.ReactElement {
<Typography>This industry uses the following equation for it's production: </Typography>
<br />
<Typography>
<IndustryProductEquation division={division} />
<IndustryProductEquation key={division.name} division={division} />
</Typography>
<br />
<Typography>

@ -13,6 +13,8 @@ import TableBody from "@mui/material/TableBody";
import { Table, TableCell } from "../../ui/React/Table";
import TableRow from "@mui/material/TableRow";
export const InvitationsSeen: string[] = [];
interface IProps {
player: IPlayer;
router: IRouter;
@ -27,6 +29,14 @@ export function FactionsRoot(props: IProps): React.ReactElement {
const id = setInterval(rerender, 200);
return () => clearInterval(id);
}, []);
useEffect(() => {
props.player.factionInvitations.forEach((faction) => {
if (InvitationsSeen.includes(faction)) return;
InvitationsSeen.push(faction);
});
}, []);
function openFaction(faction: Faction): void {
props.router.toFaction(faction);
}

@ -20,6 +20,8 @@ import { createRandomIp } from "../utils/IPAddress";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
interface IConstructorParams {
adminRights?: boolean;
hostname: string;
@ -35,7 +37,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
// Number of cores. Improves hash production
cores = 1;
// Number of hashes that can be stored by this Hacknet Server
hashCapacity = 0;
@ -93,6 +95,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
upgradeCore(levels: number, prodMult: number): void {
this.cores = Math.min(HacknetServerConstants.MaxCores, Math.round(this.cores + levels));
this.updateHashRate(prodMult);
this.cpuCores=this.cores;
}
upgradeLevel(levels: number, prodMult: number): void {

@ -21,6 +21,7 @@ import { LocationName } from "../data/LocationNames";
import { use } from "../../ui/Context";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { SnackbarEvents } from "../../ui/React/Snackbar";
type IProps = {
loc: Location;
@ -75,7 +76,7 @@ export function SpecialLocation(props: IProps): React.ReactElement {
function renderNoodleBar(): React.ReactElement {
function EatNoodles(): void {
dialogBoxCreate(<>You ate some delicious noodles and feel refreshed.</>);
SnackbarEvents.emit("You ate some delicious noodles and feel refreshed", "success");
}
return <Button onClick={EatNoodles}>Eat noodles</Button>;
@ -111,35 +112,35 @@ export function SpecialLocation(props: IProps): React.ReactElement {
function renderCotMG(): React.ReactElement {
// prettier-ignore
const symbol = <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>
{" `` "}<br />
{" -odmmNmds: "}<br />
{" `hNmo:..-omNh. "}<br />
{" yMd` `hNh "}<br />
{" mMd oNm "}<br />
{" oMNo .mM/ "}<br />
{" `dMN+ -mM+ "}<br />
{" -mMNo -mN+ "}<br />
{" .+- :mMNo/mN/ "}<br />
{":yNMd. :NMNNN/ "}<br />
{"-mMMMh. /NMMh` "}<br />
{" .dMMMd. /NMMMy` "}<br />
{" `yMMMd. /NNyNMMh` "}<br />
{" `sMMMd. +Nm: +NMMh. "}<br />
{" oMMMm- oNm: /NMMd. "}<br />
{" +NMMmsMm- :mMMd. "}<br />
{" /NMMMm- -mMMd. "}<br />
{" /MMMm- -mMMd. "}<br />
{" `sMNMMm- .mMmo "}<br />
{" `sMd:hMMm. ./. "}<br />
{" `yMy` `yNMd` "}<br />
{" `hMs` oMMy "}<br />
{" `hMh sMN- "}<br />
{" /MM- .NMo "}<br />
{" +MM: :MM+ "}<br />
{" sNNo-.`.-omNy` "}<br />
{" -smNNNNmdo- "}<br />
{" `..` "}</Typography>
const symbol = <Typography sx={{ lineHeight: '1em', whiteSpace: 'pre' }}>
{" `` "}<br />
{" -odmmNmds: "}<br />
{" `hNmo:..-omNh. "}<br />
{" yMd` `hNh "}<br />
{" mMd oNm "}<br />
{" oMNo .mM/ "}<br />
{" `dMN+ -mM+ "}<br />
{" -mMNo -mN+ "}<br />
{" .+- :mMNo/mN/ "}<br />
{":yNMd. :NMNNN/ "}<br />
{"-mMMMh. /NMMh` "}<br />
{" .dMMMd. /NMMMy` "}<br />
{" `yMMMd. /NNyNMMh` "}<br />
{" `sMMMd. +Nm: +NMMh. "}<br />
{" oMMMm- oNm: /NMMd. "}<br />
{" +NMMmsMm- :mMMd. "}<br />
{" /NMMMm- -mMMd. "}<br />
{" /MMMm- -mMMd. "}<br />
{" `sMNMMm- .mMmo "}<br />
{" `sMd:hMMm. ./. "}<br />
{" `yMy` `yNMd` "}<br />
{" `hMs` oMMy "}<br />
{" `hMh sMN- "}<br />
{" /MM- .NMo "}<br />
{" +MM: :MM+ "}<br />
{" sNNo-.`.-omNy` "}<br />
{" -smNNNNmdo- "}<br />
{" `..` "}</Typography>
return (
<>

@ -0,0 +1,27 @@
import { RunningScript } from "src/Script/RunningScript";
import { WorkerScript } from "./WorkerScript";
export const recentScripts: RecentScript[] = [];
export function AddRecentScript(workerScript: WorkerScript): void {
if (recentScripts.find((r) => r.pid === workerScript.pid)) return;
recentScripts.push({
filename: workerScript.name,
args: workerScript.args,
pid: workerScript.pid,
timestamp: new Date(),
runningScript: workerScript.scriptRef,
});
while (recentScripts.length > 50) {
recentScripts.pop();
}
}
export interface RecentScript {
filename: string;
args: string[];
pid: number;
timestamp: Date;
runningScript: RunningScript;
}

@ -11,6 +11,7 @@ import { GetServer } from "../Server/AllServers";
import { compareArrays } from "../utils/helpers/compareArrays";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { AddRecentScript } from "./RecentScripts";
export function killWorkerScript(runningScriptObj: RunningScript, hostname: string, rerenderUi?: boolean): boolean;
export function killWorkerScript(workerScript: WorkerScript): boolean;
@ -66,8 +67,6 @@ function killWorkerScriptByPid(pid: number, rerenderUi = true): boolean {
}
function stopAndCleanUpWorkerScript(workerScript: WorkerScript, rerenderUi = true): void {
workerScript.env.stopFlag = true;
killNetscriptDelay(workerScript);
if (typeof workerScript.atExit === "function") {
try {
workerScript.atExit();
@ -78,6 +77,8 @@ function stopAndCleanUpWorkerScript(workerScript: WorkerScript, rerenderUi = tru
}
workerScript.atExit = undefined;
}
workerScript.env.stopFlag = true;
killNetscriptDelay(workerScript);
removeWorkerScript(workerScript, rerenderUi);
}
@ -85,49 +86,44 @@ function stopAndCleanUpWorkerScript(workerScript: WorkerScript, rerenderUi = tru
* Helper function that removes the script being killed from the global pool.
* Also handles other cleanup-time operations
*
* @param {WorkerScript | number} - Identifier for WorkerScript. Either the object itself, or
* @param {WorkerScript} - Identifier for WorkerScript. Either the object itself, or
* its index in the global workerScripts array
*/
function removeWorkerScript(workerScript: WorkerScript, rerenderUi = true): void {
if (workerScript instanceof WorkerScript) {
const ip = workerScript.hostname;
const name = workerScript.name;
const ip = workerScript.hostname;
const name = workerScript.name;
// Get the server on which the script runs
const server = GetServer(ip);
if (server == null) {
console.error(`Could not find server on which this script is running: ${ip}`);
return;
}
// Delete the RunningScript object from that server
for (let i = 0; i < server.runningScripts.length; ++i) {
const runningScript = server.runningScripts[i];
if (runningScript.filename === name && compareArrays(runningScript.args, workerScript.args)) {
server.runningScripts.splice(i, 1);
break;
}
}
// Recalculate ram used on that server
server.ramUsed = 0;
for (const rs of server.runningScripts) server.ramUsed += rs.ramUsage * rs.threads;
// Delete script from global pool (workerScripts)
const res = workerScripts.delete(workerScript.pid);
if (!res) {
console.warn(`removeWorkerScript() called with WorkerScript that wasn't in the global map:`);
console.warn(workerScript);
}
if (rerenderUi) {
WorkerScriptStartStopEventEmitter.emit();
}
} else {
console.error(`Invalid argument passed into removeWorkerScript():`);
console.error(workerScript);
// Get the server on which the script runs
const server = GetServer(ip);
if (server == null) {
console.error(`Could not find server on which this script is running: ${ip}`);
return;
}
// Delete the RunningScript object from that server
for (let i = 0; i < server.runningScripts.length; ++i) {
const runningScript = server.runningScripts[i];
if (runningScript.filename === name && compareArrays(runningScript.args, workerScript.args)) {
server.runningScripts.splice(i, 1);
break;
}
}
// Recalculate ram used on that server
server.ramUsed = 0;
for (const rs of server.runningScripts) server.ramUsed += rs.ramUsage * rs.threads;
// Delete script from global pool (workerScripts)
const res = workerScripts.delete(workerScript.pid);
if (!res) {
console.warn(`removeWorkerScript() called with WorkerScript that wasn't in the global map:`);
console.warn(workerScript);
}
AddRecentScript(workerScript);
if (rerenderUi) {
WorkerScriptStartStopEventEmitter.emit();
}
}
/**

@ -18,7 +18,7 @@ export function makeRuntimeRejectMsg(workerScript: WorkerScript, msg: string): s
throw new Error(`WorkerScript constructed with invalid server ip: ${workerScript.hostname}`);
}
return "|" + server.hostname + "|" + workerScript.name + "|" + msg;
return "|DELIMITER|" + server.hostname + "|DELIMITER|" + workerScript.name + "|DELIMITER|" + msg;
}
export function resolveNetscriptRequestedThreads(
@ -50,7 +50,7 @@ export function isScriptErrorMessage(msg: string): boolean {
if (!isString(msg)) {
return false;
}
const splitMsg = msg.split("|");
const splitMsg = msg.split("|DELIMITER|");
if (splitMsg.length != 4) {
return false;
}

File diff suppressed because it is too large Load Diff

@ -539,7 +539,7 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS
return;
} else if (w instanceof WorkerScript) {
if (isScriptErrorMessage(w.errorMessage)) {
const errorTextArray = w.errorMessage.split("|");
const errorTextArray = w.errorMessage.split("|DELIMITER|");
if (errorTextArray.length != 4) {
console.error("ERROR: Something wrong with Error text in evaluator...");
console.error("Error text: " + w.errorMessage);

@ -59,6 +59,7 @@ import { Money } from "../../ui/React/Money";
import React from "react";
import { serverMetadata } from "../../Server/data/servers";
import { SnackbarEvents } from "../../ui/React/Snackbar";
export function init(this: IPlayer): void {
/* Initialize Player's home computer */
@ -1530,20 +1531,20 @@ export function finishCrime(this: IPlayer, cancelled: boolean): string {
if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) {
ws.scriptRef.log(
"Crime successful! Gained " +
numeralWrapper.formatMoney(this.workMoneyGained) +
", " +
numeralWrapper.formatExp(this.workHackExpGained) +
" hack exp, " +
numeralWrapper.formatExp(this.workStrExpGained) +
" str exp, " +
numeralWrapper.formatExp(this.workDefExpGained) +
" def exp, " +
numeralWrapper.formatExp(this.workDexExpGained) +
" dex exp, " +
numeralWrapper.formatExp(this.workAgiExpGained) +
" agi exp, " +
numeralWrapper.formatExp(this.workChaExpGained) +
" cha exp.",
numeralWrapper.formatMoney(this.workMoneyGained) +
", " +
numeralWrapper.formatExp(this.workHackExpGained) +
" hack exp, " +
numeralWrapper.formatExp(this.workStrExpGained) +
" str exp, " +
numeralWrapper.formatExp(this.workDefExpGained) +
" def exp, " +
numeralWrapper.formatExp(this.workDexExpGained) +
" dex exp, " +
numeralWrapper.formatExp(this.workAgiExpGained) +
" agi exp, " +
numeralWrapper.formatExp(this.workChaExpGained) +
" cha exp.",
);
}
} else {
@ -1582,18 +1583,18 @@ export function finishCrime(this: IPlayer, cancelled: boolean): string {
if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) {
ws.scriptRef.log(
"Crime failed! Gained " +
numeralWrapper.formatExp(this.workHackExpGained) +
" hack exp, " +
numeralWrapper.formatExp(this.workStrExpGained) +
" str exp, " +
numeralWrapper.formatExp(this.workDefExpGained) +
" def exp, " +
numeralWrapper.formatExp(this.workDexExpGained) +
" dex exp, " +
numeralWrapper.formatExp(this.workAgiExpGained) +
" agi exp, " +
numeralWrapper.formatExp(this.workChaExpGained) +
" cha exp.",
numeralWrapper.formatExp(this.workHackExpGained) +
" hack exp, " +
numeralWrapper.formatExp(this.workStrExpGained) +
" str exp, " +
numeralWrapper.formatExp(this.workDefExpGained) +
" def exp, " +
numeralWrapper.formatExp(this.workDexExpGained) +
" dex exp, " +
numeralWrapper.formatExp(this.workAgiExpGained) +
" agi exp, " +
numeralWrapper.formatExp(this.workChaExpGained) +
" cha exp.",
);
}
} else {
@ -1697,13 +1698,7 @@ export function regenerateHp(this: IPlayer, amt: number): void {
export function hospitalize(this: IPlayer): number {
const cost = getHospitalizationCost(this);
if (Settings.SuppressHospitalizationPopup === false) {
dialogBoxCreate(
<>
You were in critical condition! You were taken to the hospital where luckily they were able to save your life.
You were charged&nbsp;
<Money money={cost} />
</>,
);
SnackbarEvents.emit(`You've been Hospitalized for ${numeralWrapper.formatMoney(cost)}`, "warning");
}
this.loseMoney(cost);

@ -122,7 +122,7 @@ const tasks: {
};
},
"Commit Crime": (): ITaskDetails => {
return { first: Object.keys(Crimes), second: () => ["------"] };
return { first: Object.values(Crimes).map((crime) => crime.name), second: () => ["------"] };
},
"Take University Course": (player: IPlayer, sleeve: Sleeve): ITaskDetails => {
let universities: string[] = [];

@ -27,11 +27,13 @@ import { Terminal } from "./Terminal";
import { dialogBoxCreate } from "./ui/React/DialogBox";
import Decimal from "decimal.js";
import { ProgramsSeen } from "./Programs/ui/ProgramsRoot";
import { InvitationsSeen } from "./Faction/ui/FactionsRoot";
const BitNode8StartingMoney = 250e6;
// Prestige by purchasing augmentation
function prestigeAugmentation(): void {
export function prestigeAugmentation(): void {
initBitNodeMultipliers(Player);
const maintainMembership = Player.factions.filter(function (faction) {
@ -138,10 +140,12 @@ function prestigeAugmentation(): void {
}
resetPidCounter();
ProgramsSeen.splice(0, ProgramsSeen.length);
InvitationsSeen.splice(0, InvitationsSeen.length);
}
// Prestige by destroying Bit Node and gaining a Source File
function prestigeSourceFile(flume: boolean): void {
export function prestigeSourceFile(flume: boolean): void {
initBitNodeMultipliers(Player);
updateSourceFileFlags(Player);
@ -265,5 +269,3 @@ function prestigeSourceFile(flume: boolean): void {
resetPidCounter();
}
export { prestigeAugmentation, prestigeSourceFile };

@ -5,6 +5,8 @@ import { getAvailableCreatePrograms } from "../ProgramHelpers";
import { Tooltip, Typography } from "@mui/material";
import Button from "@mui/material/Button";
export const ProgramsSeen: string[] = [];
export function ProgramsRoot(): React.ReactElement {
const player = use.Player();
const router = use.Router();
@ -13,6 +15,15 @@ export function ProgramsRoot(): React.ReactElement {
setRerender((old) => !old);
}
const programs = getAvailableCreatePrograms(player);
useEffect(() => {
programs.forEach((p) => {
if (ProgramsSeen.includes(p.name)) return;
ProgramsSeen.push(p.name);
});
}, []);
useEffect(() => {
const id = setInterval(rerender, 200);
return () => clearInterval(id);
@ -27,7 +38,7 @@ export function ProgramsRoot(): React.ReactElement {
time. Your progress will be saved and you can continue later.
</Typography>
{getAvailableCreatePrograms(player).map((program) => {
{programs.map((program) => {
const create = program.create;
if (create === null) return <></>;

@ -3249,7 +3249,7 @@ interface NS extends TIX, Singularity {
* hack("foodnstuff", { threads: 5 }); // Only use 5 threads to hack
* \`\`\`
* @ramCost 0.1 GB
* @param {string} host IP or hostname of the target server to hack.
* @param {string} host Hostname or IP of the target server to hack.
* @param {object} [opts] Optional parameters for configuring function behavior.
* @returns {Promise<number>} The amount of money stolen if the hack is successful, and zero otherwise.
*/
@ -3274,7 +3274,7 @@ interface NS extends TIX, Singularity {
* grow("foodnstuff", { threads: 5 }); // Only use 5 threads to grow
* \`\`\`
* @ramCost 0.15 GB
* @param {string} host IP or hostname of the target server to grow.
* @param {string} host Hostname or IP of the target server to grow.
* @param {object} [opts] Optional parameters for configuring function behavior.
* @returns {Promise<number>} The number by which the money on the server was multiplied for the growth.
*/
@ -3295,7 +3295,7 @@ interface NS extends TIX, Singularity {
* weaken("foodnstuff", { threads: 5 }); // Only use 5 threads to weaken
* \`\`\`
* @ramCost 0.15 GB
* @param {string} host IP or hostname of the target server to weaken.
* @param {string} host Hostname or IP of the target server to weaken.
* @param {object} [opts] Optional parameters for configuring function behavior.
* @returns {Promise<number>} The amount by which the target servers security level was decreased. This is equivalent to 0.05 multiplied by the number of script threads.
*/
@ -3316,7 +3316,7 @@ interface NS extends TIX, Singularity {
* //If this function returns 50, this means that if your next {@link hack} call is run on a script with 50 threads, it will steal $1m from the foodnstuff server.
* \`\`\`
* @ramCost 1 GB
* @param {string} host IP or hostname of the target server to analyze.
* @param {string} host Hostname or IP of the target server to analyze.
* @param {number} hackAmount Amount of money you want to hack from the server.
* @returns {number} The number of threads needed to {@link hack} the server for hackAmount money.
*/
@ -3334,7 +3334,7 @@ interface NS extends TIX, Singularity {
* //This means that if hack the foodnstuff server, then you will steal 1% of its total money. If you {@link hack} using N threads, then you will steal N% of its total money.
* \`\`\`
* @ramCost 1 GB
* @param {string} host IP or hostname of the target server.
* @param {string} host Hostname or IP of the target server.
* @returns {number} The percentage of money you will steal from the target server with a single hack.
*/
hackAnalyzePercent (host: Host): number;
@ -3345,7 +3345,7 @@ interface NS extends TIX, Singularity {
* This returned value is in decimal form, not percentage.
*
* @ramCost 1 GB
* @param {string} host IP or hostname of the target server.
* @param {string} host Hostname or IP of the target server.
* @returns {number} The chance you have of successfully hacking the target server.
*/
hackChance (host: Host): number;
@ -3364,7 +3364,7 @@ interface NS extends TIX, Singularity {
* //If this returns 100, then this means you need to call {@link grow} 100 times in order to double the money (or once with 100 threads).
* \`\`\`
* @ramCost 1 GB
* @param {string} host IP or hostname of the target server.
* @param {string} host Hostname or IP of the target server.
* @param {number} growthAmount Multiplicative factor by which the server is grown. Decimal form..
* @returns {number} The amount of {@link grow} calls needed to grow the specified server by the specified amount
*/
@ -3462,11 +3462,11 @@ interface NS extends TIX, Singularity {
* \`\`\`
* @ramCost 0 GB
* @param {string} [fn] Optional. Filename of script to get logs from.
* @param {string} [ip] Optional. IP or hostname of the server that the script is on.
* @param {string} [host] Optional. Hostname or IP of the server that the script is on.
* @param {...string} [args] Arguments to identify which scripts to get logs for.
* @returns {string[]} Returns an string array, where each line is an element in the array. The most recently logged line is at the end of the array.
*/
getScriptLogs (fn?: Script, ip?: Host, ...args: any[]): string[];
getScriptLogs (fn?: Script, host?: Host, ...args: any[]): string[];
/**
* Opens a scripts logs. This is functionally the same as the tail Terminal command.
@ -3492,10 +3492,11 @@ interface NS extends TIX, Singularity {
* tail("foo.script", "foodnstuff", 1, "test");
* \`\`\`
* @ramCost 0 GB
* @param {string} host IP or hostname of the server to scan.
* @param {boolean} hostnames Optional boolean specifying whether the function should output hostnames (if true) or IP addresses (if false).
* @param {string} [fn] Optional. Filename of the script being tailed. If omitted, the current script is tailed.
* @param {string} [host] Optional. Hostname or IP of the script being tailed. Defaults to the server this script is running on. If args are specified, this is not optional.
* @param {any[]} ...args Arguments for the script being tailed.
*/
tail (fn?: Script, ip?: Host, ...args: any[]): void;
tail (fn?: Script, host?: Host, ...args: any[]): void;
/**
* Returns an array containing the hostnames or IPs of all servers that are one
@ -3503,7 +3504,7 @@ interface NS extends TIX, Singularity {
* array are strings.
*
* @ramCost 0.2 GB
* @param {string} host IP or hostname of the server to scan.
* @param {string} host Hostname or IP of the server to scan.
* @param {boolean} hostnames Optional boolean specifying whether the function should output hostnames (if true) or IP addresses (if false).
* @returns {string[]} Returns an string of hostnames or IP.
*/
@ -3517,7 +3518,7 @@ interface NS extends TIX, Singularity {
* nuke("foodnstuff");
* \`\`\`
* @ramCost 0.05 GB
* @param {string} host IP or hostname of the target server.
* @param {string} host Hostname or IP of the target server.
*/
nuke (host: Host): void;
@ -3529,7 +3530,7 @@ interface NS extends TIX, Singularity {
* brutessh("foodnstuff");
* \`\`\`
* @ramCost 0.05 GB
* @param {string} host IP or hostname of the target server.
* @param {string} host Hostname or IP of the target server.
*/
brutessh (host: Host): void;
@ -3541,7 +3542,7 @@ interface NS extends TIX, Singularity {
* ftpcrack("foodnstuff");
* \`\`\`
* @ramCost 0.05 GB
* @param {string} host IP or hostname of the target server.
* @param {string} host Hostname or IP of the target server.
*/
ftpcrack (host: Host): void;
@ -3553,7 +3554,7 @@ interface NS extends TIX, Singularity {
* relaysmtp("foodnstuff");
* \`\`\`
* @ramCost 0.05 GB
* @param {string} host IP or hostname of the target server.
* @param {string} host Hostname or IP of the target server.
*/
relaysmtp (host: Host): void;
@ -3565,7 +3566,7 @@ interface NS extends TIX, Singularity {
* httpworm("foodnstuff");
* \`\`\`
* @ramCost 0.05 GB
* @param {string} host IP or hostname of the target server.
* @param {string} host Hostname or IP of the target server.
*/
httpworm (host: Host): void;
@ -3577,7 +3578,7 @@ interface NS extends TIX, Singularity {
* sqlinject("foodnstuff");
* \`\`\`
* @ramCost 0.05 GB
* @param {string} host IP or hostname of the target server.
* @param {string} host Hostname or IP of the target server.
*/
sqlinject (host: Host): void;
@ -3652,7 +3653,7 @@ interface NS extends TIX, Singularity {
* \`\`\`
* @ramCost 1.3 GB
* @param {string} script Filename of script to execute.
* @param {string} host IP or hostname of the \`target server\` on which to execute the script.
* @param {string} host Hostname or IP of the \`target server\` on which to execute the script.
* @param {number} [numThreads] Optional thread count for new script. Set to 1 by default. Will be rounded to nearest integer.
* @param {...string} [args] Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the third argument numThreads must be filled in with a value.
* @returns {number} Returns the PID of a successfully started script, and 0 otherwise.
@ -3707,7 +3708,7 @@ interface NS extends TIX, Singularity {
* \`\`\`
* @ramCost 0.5 GB
* @param {string} script Filename of the script to kill
* @param {string} host IP or hostname of the server on which to kill the script.
* @param {string} host Hostname or IP of the server on which to kill the script.
* @param {...string} [args] Arguments to identify which script to kill.
* @returns {boolean} True if the script is successfully killed, and false otherwise.
*/
@ -3890,6 +3891,16 @@ interface NS extends TIX, Singularity {
*/
getHacknetMultipliers (): HacknetMultipliers;
/**
* Returns a server object for the given server. Defaults to the running script's server if host is not specified.
*
* @ramCost 2 GB
* @param {string} [host] Optional. Hostname or IP for the requested server object.
* @returns {Server} The requested server object.
*/
getServer (host?: Host): Server;
/**
* Returns the amount of money available on a server.
* Running this function on the home computer will return the players money.
@ -4128,10 +4139,10 @@ interface NS extends TIX, Singularity {
* Returns an array with either the hostnames or IPs of all of the servers you have purchased.
*
* @ramCost 2.25 GB
* @param {boolean} hostname Specifies whether hostnames or IP addresses should be returned. If its true then hostnames will be returned, and if false then IPs will be returned. If this argument is omitted then it is true by default.
* @param {boolean} [hostnameMode]. Optional. Defaults to true. Returns hostnames if true, and IPs if false.
* @returns {string[]} Returns an array with either the hostnames or IPs of all of the servers you have purchased.
*/
getPurchasedServers (hostname?: boolean): Host[];
getPurchasedServers (hostnameMode?: boolean): Host[];
/**
* Returns the maximum number of servers you can purchase.

@ -41,10 +41,12 @@ export function OptionsModal(props: IProps): React.ReactElement {
<Modal open={props.open} onClose={props.onClose}>
<Box display="flex" flexDirection="row" alignItems="center">
<Typography>Theme: </Typography>
<Select onChange={(event) => setTheme(event.target.value)} defaultValue={props.options.theme}>
<Select onChange={(event) => setTheme(event.target.value)} value={theme}>
<MenuItem value="vs-dark">dark</MenuItem>
<MenuItem value="light">light</MenuItem>
<MenuItem value="monokai">monokai</MenuItem>
<MenuItem value="solarized-dark">solarized-dark</MenuItem>
<MenuItem value="solarized-light">solarized-light</MenuItem>
</Select>
</Box>

@ -4,7 +4,6 @@ import * as monaco from "monaco-editor";
type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;
import { OptionsModal } from "./OptionsModal";
import { Options } from "./Options";
import { js_beautify as beautifyCode } from "js-beautify";
import { isValidFilePath } from "../../Terminal/DirectoryHelpers";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IRouter } from "../../ui/Router";
@ -217,12 +216,7 @@ export function Root(props: IProps): React.ReactElement {
function beautify(): void {
if (editorRef.current === null) return;
const pretty = beautifyCode(code, {
indent_with_tabs: !options.insertSpaces,
indent_size: 4,
brace_style: "preserve-inline",
});
editorRef.current.setValue(pretty);
editorRef.current.getAction("editor.action.formatDocument").run();
}
function onFilenameChange(event: React.ChangeEvent<HTMLInputElement>): void {

@ -63,4 +63,159 @@ export async function loadThemes(monaco: { editor: any }): Promise<void> {
"editor.selectionHighlightBorder": "#222218",
},
});
monaco.editor.defineTheme("solarish-dark", {
base: "vs-dark",
inherit: true,
rules: [
{
background: "002b36",
token: "",
},
{
foreground: "586e75",
token: "comment",
},
{
foreground: "00afaf",
token: "string",
},
{
token: "number",
foreground: "00afaf",
},
{
token: "otherkeyvars",
foreground: "268bd2",
},
{
foreground: "268bd2",
token: "function",
},
{
foreground: "859900",
token: "keyword",
},
{
token: "storage.type.function.js",
foreground: "cb4b16",
},
{
token: "ns",
foreground: "cb4b16",
},
{
token: "netscriptfunction",
foreground: "268bd2",
},
{
token: "otherkeywords",
foreground: "268bd2",
},
{
token: "type.identifier.js",
foreground: "b58900",
},
{
token: "delimiter.square.js",
foreground: "0087ff",
},
{
token: "delimiter.bracket.js",
foreground: "0087ff",
},
{
token: "this",
foreground: "cb4b16",
},
],
colors: {
"editor.foreground": "#839496",
"editor.background": "#002b36",
"editor.selectionBackground": "#073642",
"editor.lineHighlightBackground": "#073642",
"editorCursor.foreground": "#819090",
"editorWhitespace.foreground": "#073642",
"editorIndentGuide.activeBackground": "#9D550FB0",
"editor.selectionHighlightBorder": "#222218",
},
});
monaco.editor.defineTheme("solarish-light", {
base: "vs",
inherit: true,
rules: [
{
foreground: "657b83",
background: "fdf6e3",
token: "",
},
{
foreground: "586e75",
token: "comment",
},
{
foreground: "2aa198",
token: "string",
},
{
token: "number",
foreground: "2aa198",
},
{
token: "otherkeyvars",
foreground: "268bd2",
},
{
foreground: "268bd2",
token: "function",
},
{
foreground: "859900",
token: "keyword",
},
{
token: "storage.type.function.js",
foreground: "bc4b16",
},
{
token: "ns",
foreground: "cb4b16",
},
{
token: "netscriptfunction",
foreground: "268bd2",
},
{
token: "otherkeywords",
foreground: "268bd2",
},
{
token: "type.identifier.js",
foreground: "b58900",
},
{
token: "delimiter.square.js",
foreground: "0087ff",
},
{
token: "delimiter.bracket.js",
foreground: "0087ff",
},
{
token: "this",
foreground: "cb4b16",
},
],
colors: {
"editor.foreground": "#657b83",
"editor.background": "#fdf6e3",
"editor.selectionBackground": "#eee8d5",
"editor.lineHighlightBackground": "#eee8d5",
"editorCursor.foreground": "#657b83",
"editorWhitespace.foreground": "#eee8d5",
"editorIndentGuide.activeBackground": "#eee8d5",
"editor.selectionHighlightBorder": "#073642",
},
});
}

@ -51,6 +51,8 @@ import { Settings } from "../../Settings/Settings";
import { redPillFlag } from "../../RedPill";
import { KEY } from "../../utils/helpers/keyCodes";
import { ProgramsSeen } from "../../Programs/ui/ProgramsRoot";
import { InvitationsSeen } from "../../Faction/ui/FactionsRoot";
const openedMixin = (theme: Theme): CSSObject => ({
width: theme.spacing(31),
@ -133,8 +135,8 @@ export function SidebarRoot(props: IProps): React.ReactElement {
const flashTutorial = ITutorial.currStep === iTutorialSteps.WorldDescription;
const augmentationCount = props.player.queuedAugmentations.length;
const invitationsCount = props.player.factionInvitations.length;
const programCount = getAvailableCreatePrograms(props.player).length;
const invitationsCount = props.player.factionInvitations.length - InvitationsSeen.length;
const programCount = getAvailableCreatePrograms(props.player).length - ProgramsSeen.length;
const canCreateProgram =
programCount > 0 ||
props.player.augmentations.length > 0 ||

@ -0,0 +1,31 @@
/**
* Root React Component for the "Active Scripts" UI page. This page displays
* and provides information about all of the player's scripts that are currently running
*/
import React from "react";
import { ScriptProduction } from "./ScriptProduction";
import { ServerAccordions } from "./ServerAccordions";
import { WorkerScript } from "../../Netscript/WorkerScript";
import Typography from "@mui/material/Typography";
interface IProps {
workerScripts: Map<number, WorkerScript>;
}
export function ActiveScriptsPage(props: IProps): React.ReactElement {
return (
<>
<Typography>
This page displays a list of all of your scripts that are currently running across every machine. It also
provides information about each script's production. The scripts are categorized by the hostname of the servers
on which they are running.
</Typography>
<ScriptProduction />
<ServerAccordions {...props} />
</>
);
}

@ -3,17 +3,16 @@
* and provides information about all of the player's scripts that are currently running
*/
import React, { useState, useEffect } from "react";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import { ScriptProduction } from "./ScriptProduction";
import { ServerAccordions } from "./ServerAccordions";
import { ActiveScriptsPage } from "./ActiveScriptsPage";
import { RecentScriptsPage } from "./RecentScriptsPage";
import { WorkerScript } from "../../Netscript/WorkerScript";
import Typography from "@mui/material/Typography";
type IProps = {
interface IProps {
workerScripts: Map<number, WorkerScript>;
};
}
export function ActiveScriptsRoot(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
@ -26,17 +25,19 @@ export function ActiveScriptsRoot(props: IProps): React.ReactElement {
return () => clearInterval(id);
}, []);
const [tab, setTab] = useState<"active" | "recent">("active");
function handleChange(event: React.SyntheticEvent, tab: "active" | "recent"): void {
setTab(tab);
}
return (
<>
<Typography variant="h4">Active Scripts</Typography>
<Typography>
This page displays a list of all of your scripts that are currently running across every machine. It also
provides information about each script's production. The scripts are categorized by the hostname of the servers
on which they are running.
</Typography>
<Tabs variant="fullWidth" value={tab} onChange={handleChange}>
<Tab label={"Active"} value={"active"} />
<Tab label={"Recent"} value={"recent"} />
</Tabs>
<ScriptProduction />
<ServerAccordions {...props} />
{tab === "active" && <ActiveScriptsPage workerScripts={props.workerScripts} />}
{tab === "recent" && <RecentScriptsPage />}
</>
);
}

@ -0,0 +1,165 @@
/**
* React Component for displaying a single WorkerScript's info as an
* Accordion element
*/
import * as React from "react";
import { numeralWrapper } from "../numeralFormat";
import Table from "@mui/material/Table";
import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow";
import TableBody from "@mui/material/TableBody";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import makeStyles from "@mui/styles/makeStyles";
import Collapse from "@mui/material/Collapse";
import ExpandLess from "@mui/icons-material/ExpandLess";
import ExpandMore from "@mui/icons-material/ExpandMore";
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { arrayToString } from "../../utils/helpers/arrayToString";
import { Money } from "../React/Money";
import { MoneyRate } from "../React/MoneyRate";
import { RecentScript } from "../..//Netscript/RecentScripts";
import { LogBoxEvents } from "../React/LogBoxManager";
const useStyles = makeStyles({
noborder: {
borderBottom: "none",
},
});
interface IProps {
recentScript: RecentScript;
}
export function RecentScriptAccordion(props: IProps): React.ReactElement {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const recentScript = props.recentScript;
// Calculations for script stats
const onlineMps = recentScript.runningScript.onlineMoneyMade / recentScript.runningScript.onlineRunningTime;
const onlineEps = recentScript.runningScript.onlineExpGained / recentScript.runningScript.onlineRunningTime;
function logClickHandler(): void {
LogBoxEvents.emit(recentScript.runningScript);
}
return (
<>
<ListItemButton onClick={() => setOpen((old) => !old)} component={Paper}>
<ListItemText
primary={
<Typography>
{recentScript.filename} (died{" "}
{convertTimeMsToTimeElapsedString(new Date().getTime() - recentScript.timestamp.getTime())} ago)
</Typography>
}
/>
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
</ListItemButton>
<Collapse in={open} timeout={0} unmountOnExit>
<Box mx={6}>
<Table padding="none" size="small">
<TableBody>
<TableRow>
<TableCell className={classes.noborder}>
<Typography> Threads:</Typography>
</TableCell>
<TableCell className={classes.noborder}>
<Typography>{numeralWrapper.formatThreads(recentScript.runningScript.threads)}</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell className={classes.noborder} colSpan={2}>
<Typography> Args: {arrayToString(recentScript.args)}</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell className={classes.noborder}>
<Typography> Online Time:</Typography>
</TableCell>
<TableCell className={classes.noborder}>
<Typography>
{convertTimeMsToTimeElapsedString(recentScript.runningScript.onlineRunningTime * 1e3)}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell className={classes.noborder}>
<Typography> Offline Time:</Typography>
</TableCell>
<TableCell className={classes.noborder}>
<Typography>
{convertTimeMsToTimeElapsedString(recentScript.runningScript.offlineRunningTime * 1e3)}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell className={classes.noborder}>
<Typography> Total online production:</Typography>
</TableCell>
<TableCell className={classes.noborder} align="left">
<Typography>
<Money money={recentScript.runningScript.onlineMoneyMade} />
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell className={classes.noborder} colSpan={1} />
<TableCell className={classes.noborder} align="left">
<Typography>
&nbsp;{numeralWrapper.formatExp(recentScript.runningScript.onlineExpGained) + " hacking exp"}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell className={classes.noborder}>
<Typography> Online production rate:</Typography>
</TableCell>
<TableCell className={classes.noborder} align="left">
<Typography>
<MoneyRate money={onlineMps} />
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell className={classes.noborder} colSpan={1} />
<TableCell className={classes.noborder} align="left">
<Typography>&nbsp;{numeralWrapper.formatExp(onlineEps) + " hacking exp / sec"}</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell className={classes.noborder}>
<Typography> Total offline production:</Typography>
</TableCell>
<TableCell className={classes.noborder} align="left">
<Typography>
<Money money={recentScript.runningScript.offlineMoneyMade} />
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell className={classes.noborder} colSpan={1} />
<TableCell className={classes.noborder} align="left">
<Typography>
&nbsp;{numeralWrapper.formatExp(recentScript.runningScript.offlineExpGained) + " hacking exp"}
</Typography>
</TableCell>
</TableRow>
</TableBody>
</Table>
<Button onClick={logClickHandler}>LOG</Button>
</Box>
</Collapse>
</>
);
}

@ -0,0 +1,20 @@
/**
* Root React Component for the "Active Scripts" UI page. This page displays
* and provides information about all of the player's scripts that are currently running
*/
import React from "react";
import Typography from "@mui/material/Typography";
import { recentScripts } from "../../Netscript/RecentScripts";
import { RecentScriptAccordion } from "./RecentScriptAccordion";
export function RecentScriptsPage(): React.ReactElement {
return (
<>
<Typography>List of all recently killed scripts.</Typography>
{recentScripts.map((r) => (
<RecentScriptAccordion key={r.pid} recentScript={r} />
))}
</>
);
}

@ -302,7 +302,7 @@ export function refreshTheme(): void {
border: "1px solid " + Settings.theme.well,
},
standardSuccess: {
color: Settings.theme.primaryLight,
color: Settings.theme.primarylight,
},
standardError: {
color: Settings.theme.errorlight,

@ -222,7 +222,7 @@ export function WorkInProgressRoot(): React.ReactElement {
if (player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
function cancel(): void {
player.finishWork(true);
player.finishWorkPartTime(true);
router.toJob();
}
function unfocus(): void {