merge dev

This commit is contained in:
Olivier Gagnon 2021-10-26 23:11:47 -04:00
commit 080b5a4a1f
82 changed files with 1280 additions and 793 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

@ -27,6 +27,10 @@ to attempt infiltrations above mid-normal.
** Slash when his guard is down! **
Press space when the guard is attacking you.
There's 3 phase
The first is guarding, where attacking back will result in failure.
The 2nd is preparing, this informs you that in 250ms there will be an opening window to attack.
The 3rd is attack, during this phase you can press space to slash and kill the enemy.
** Close the brackets **

@ -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>`)

@ -7,7 +7,7 @@ toast() Netscript Function
:param string message: message to display
:param success|info|warning|error variant: color of the toast
Spawns a toast (those bottom right notifications).
Spawns a toast (those bottom right notifications, like "Game Saved!" ).
Example:

@ -0,0 +1,15 @@
getCityCommunities() Netscript Function
================================================
.. js:function:: getCityCommunities(cityName)
:RAM cost: 4 GB
:param string cityName: Name of city. Case-sensitive
:returns: Confirmed number of Synthoid communities in the specified city,
or -1 if an invalid city was specified.
Example:
.. code-block:: javascript
bladeburner.getCityCommunities("Sector-12"); // returns: 76

@ -1,15 +0,0 @@
getCityEstimatedCommunities() Netscript Function
================================================
.. js:function:: getCityEstimatedCommunities(cityName)
:RAM cost: 4 GB
:param string cityName: Name of city. Case-sensitive
:returns: Estimated number of Synthoid communities in the specified city,
or -1 if an invalid city was specified.
Example:
.. code-block:: javascript
bladeburner.getCityEstimatedCommunities("Sector-12"); // returns: 76

@ -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

1
package-lock.json generated

@ -5,6 +5,7 @@
"requires": true,
"packages": {
"": {
"name": "bitburner",
"version": "0.56.0",
"hasInstallScript": true,
"license": "SEE LICENSE IN license.txt",

@ -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",

@ -32,7 +32,7 @@ export function SourceFileMinus1(): React.ReactElement {
<Typography style={{ whiteSpace: "pre-wrap" }}>
Source-File -1: Exploits in the BitNodes
<br />
Level {exploits.length} / ?
Level {exploits.length} / {Object.keys(Exploit).length}
</Typography>
}
/>

@ -598,10 +598,9 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.InfiltrationMoney = 3;
BitNodeMultipliers.FactionWorkRepGain = 0.5;
BitNodeMultipliers.FactionPassiveRepGain = 0;
BitNodeMultipliers.GangKarmaRequirement = 0;
BitNodeMultipliers.PurchasedServerSoftcap = 1.4;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 2;
BitNodeMultipliers.StaneksGiftExtraSize = -6;
BitNodeMultipliers.PurchasedServerSoftcap = 1.3;
break;
case 3: // Corporatocracy
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
@ -617,10 +616,10 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.HacknetNodeMoney = 0.25;
BitNodeMultipliers.HomeComputerRamCost = 1.5;
BitNodeMultipliers.PurchasedServerCost = 2;
BitNodeMultipliers.GangKarmaRequirement = 3;
BitNodeMultipliers.PurchasedServerSoftcap = 1.4;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.75;
BitNodeMultipliers.StaneksGiftExtraSize = -2;
BitNodeMultipliers.PurchasedServerSoftcap = 1.3;
BitNodeMultipliers.GangSoftcap = 0.9;
break;
case 4: // The Singularity
BitNodeMultipliers.ServerMaxMoney = 0.15;
@ -635,9 +634,9 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.HackExpGain = 0.4;
BitNodeMultipliers.CrimeExpGain = 0.5;
BitNodeMultipliers.FactionWorkRepGain = 0.75;
BitNodeMultipliers.PurchasedServerSoftcap = 1.3;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 1.5;
BitNodeMultipliers.StaneksGiftExtraSize = 0;
BitNodeMultipliers.PurchasedServerSoftcap = 1.2;
break;
case 5: // Artificial intelligence
BitNodeMultipliers.ServerMaxMoney = 2;
@ -651,9 +650,9 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.AugmentationMoneyCost = 2;
BitNodeMultipliers.HackExpGain = 0.5;
BitNodeMultipliers.CorporationValuation = 0.5;
BitNodeMultipliers.PurchasedServerSoftcap = 1.3;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 1.3;
BitNodeMultipliers.StaneksGiftExtraSize = 0;
BitNodeMultipliers.PurchasedServerSoftcap = 1.2;
break;
case 6: // Bladeburner
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
@ -669,10 +668,10 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.FactionPassiveRepGain = 0;
BitNodeMultipliers.HackExpGain = 0.25;
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
BitNodeMultipliers.GangKarmaRequirement = 5;
BitNodeMultipliers.PurchasedServerSoftcap = 2;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.5;
BitNodeMultipliers.StaneksGiftExtraSize = 2;
BitNodeMultipliers.GangSoftcap = 0.7;
break;
case 7: // Bladeburner 2079
BitNodeMultipliers.BladeburnerRank = 0.6;
@ -693,10 +692,10 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.FourSigmaMarketDataCost = 2;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
BitNodeMultipliers.GangKarmaRequirement = 5;
BitNodeMultipliers.PurchasedServerSoftcap = 2;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.9;
BitNodeMultipliers.StaneksGiftExtraSize = -1;
BitNodeMultipliers.GangSoftcap = 0.7;
break;
case 8: // Ghost of Wall Street
BitNodeMultipliers.ScriptHackMoney = 0.3;
@ -709,9 +708,9 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.RepToDonateToFaction = 0;
BitNodeMultipliers.CorporationValuation = 0;
BitNodeMultipliers.CodingContractMoney = 0;
BitNodeMultipliers.GangKarmaRequirement = 10;
BitNodeMultipliers.PurchasedServerSoftcap = 5;
BitNodeMultipliers.StaneksGiftExtraSize = -7;
BitNodeMultipliers.PurchasedServerSoftcap = 4;
BitNodeMultipliers.GangSoftcap = 0;
break;
case 9: // Hacktocracy
BitNodeMultipliers.HackingLevelMultiplier = 0.4;
@ -733,9 +732,9 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
BitNodeMultipliers.BladeburnerRank = 0.9;
BitNodeMultipliers.BladeburnerSkillCost = 1.2;
BitNodeMultipliers.GangKarmaRequirement = 3;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.5;
BitNodeMultipliers.StaneksGiftExtraSize = 2;
BitNodeMultipliers.GangSoftcap = 0.8;
break;
case 10: // Digital Carbon
BitNodeMultipliers.HackingLevelMultiplier = 0.2;
@ -759,10 +758,10 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.PurchasedServerLimit = 0.6;
BitNodeMultipliers.PurchasedServerMaxRam = 0.5;
BitNodeMultipliers.BladeburnerRank = 0.8;
BitNodeMultipliers.GangKarmaRequirement = 3;
BitNodeMultipliers.PurchasedServerSoftcap = 1.2;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.75;
BitNodeMultipliers.StaneksGiftExtraSize = -3;
BitNodeMultipliers.PurchasedServerSoftcap = 1.1;
BitNodeMultipliers.GangSoftcap = 0.9;
break;
case 11: //The Big Crash
BitNodeMultipliers.HackingLevelMultiplier = 0.5;
@ -781,7 +780,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.CodingContractMoney = 0.25;
BitNodeMultipliers.FourSigmaMarketDataCost = 4;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
BitNodeMultipliers.PurchasedServerSoftcap = 2.2;
BitNodeMultipliers.PurchasedServerSoftcap = 2;
break;
case 12: {
//The Recursion
@ -853,6 +852,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.StaneksGiftPowerMultiplier = inc;
BitNodeMultipliers.StaneksGiftExtraSize = inc;
BitNodeMultipliers.GangSoftcap = dec;
break;
}
case 13: {
@ -893,7 +893,6 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.BladeburnerRank = 0.1;
BitNodeMultipliers.BladeburnerSkillCost = 5;
BitNodeMultipliers.GangKarmaRequirement = 20;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 2;
BitNodeMultipliers.StaneksGiftExtraSize = 1;
break;

@ -110,9 +110,9 @@ interface IBitNodeMultipliers {
FourSigmaMarketDataCost: number;
/**
* Influences how much negative karma is required to create a gang in this bitnode.
* Reduces gangs earning.
*/
GangKarmaRequirement: number;
GangSoftcap: number;
/**
* Influences the experienced gained when hacking a server.
@ -288,8 +288,9 @@ export const BitNodeMultipliers: IBitNodeMultipliers = {
BladeburnerRank: 1,
BladeburnerSkillCost: 1,
GangSoftcap: 1,
DaedalusAugsRequirement: 1,
GangKarmaRequirement: 1,
StaneksGiftPowerMultiplier: 1,
StaneksGiftExtraSize: 0,

@ -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;
}

@ -1503,18 +1503,22 @@ export class Bladeburner implements IBladeburner {
for (const contract of Object.keys(this.contracts)) {
const growthF = Growths[contract];
if (!growthF) throw new Error("trying to generate count for action that doesn't exist? " + contract);
this.contracts[contract].count += (60 * 6 * growthF()) / BladeburnerConstants.ActionCountGrowthPeriod;
this.contracts[contract].count += (60 * 3 * growthF()) / BladeburnerConstants.ActionCountGrowthPeriod;
}
for (const operation of Object.keys(this.operations)) {
const growthF = Growths[operation];
if (!growthF) throw new Error("trying to generate count for action that doesn't exist? " + operation);
this.operations[operation].count += (60 * 6 * growthF()) / BladeburnerConstants.ActionCountGrowthPeriod;
this.operations[operation].count += (60 * 3 * growthF()) / BladeburnerConstants.ActionCountGrowthPeriod;
}
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;
}

@ -3,35 +3,17 @@ import { IMap } from "../types";
export const GeneralActions: IMap<Action> = {};
(function () {
// General Actions
let actionName;
actionName = "Training";
GeneralActions[actionName] = new Action({
name: actionName,
});
let actionNames : Array<string> = [
"Training",
"Field Analysis",
"Recruitment",
"Diplomacy",
"Hyperbolic Regeneration Chamber",
"Incite Violence"
];
actionName = "Field Analysis";
for (let actionName of actionNames){
GeneralActions[actionName] = new Action({
name: actionName,
});
actionName = "Recruitment";
GeneralActions[actionName] = new Action({
name: actionName,
});
actionName = "Diplomacy";
GeneralActions[actionName] = new Action({
name: actionName,
});
actionName = "Hyperbolic Regeneration Chamber";
GeneralActions[actionName] = new Action({
name: actionName,
});
actionName = "Incite Violence";
GeneralActions[actionName] = new Action({
name: actionName,
});
})();
}

@ -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;
}

@ -12,13 +12,13 @@ export const Growths: {
["Stealth Retirement Operation"]: () => number;
["Assassination"]: () => number;
} = {
Tracking: () => getRandomInt(5, 75) / 20,
"Bounty Hunter": () => getRandomInt(5, 75) / 20,
Retirement: () => getRandomInt(5, 75) / 20,
Investigation: () => getRandomInt(10, 40) / 20,
"Undercover Operation": () => getRandomInt(10, 40) / 20,
"Sting Operation": () => getRandomInt(3, 40) / 20,
Raid: () => getRandomInt(2, 40) / 20,
"Stealth Retirement Operation": () => getRandomInt(1, 20) / 20,
Assassination: () => getRandomInt(1, 20) / 20,
Tracking: () => getRandomInt(5, 75) / 10,
"Bounty Hunter": () => getRandomInt(5, 75) / 10,
Retirement: () => getRandomInt(5, 75) / 10,
Investigation: () => getRandomInt(10, 40) / 10,
"Undercover Operation": () => getRandomInt(10, 40) / 10,
"Sting Operation": () => getRandomInt(3, 40) / 10,
Raid: () => getRandomInt(2, 40) / 10,
"Stealth Retirement Operation": () => getRandomInt(1, 20) / 10,
Assassination: () => getRandomInt(1, 20) / 10,
};

@ -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>

@ -53,8 +53,8 @@ export function IssueDividendsModal(props: IProps): React.ReactElement {
<br />
<br />
In order to issue dividends, simply allocate some percentage of your corporation's profits to dividends. This
percentage must be an integer between 0 and {CorporationConstants.DividendMaxPercentage}. (A percentage of 0
means no dividends will be issued
percentage must be an integer between 0 and 50. (A percentage of 0
means no dividends will be issued)
<br />
<br />
Two important things to note:

@ -16,7 +16,6 @@ interface IMarketTA2Props {
function MarketTA2(props: IMarketTA2Props): React.ReactElement {
const division = useDivision();
if (!division.hasResearch("Market-TA.II")) return <></>;
const [newCost, setNewCost] = useState<number>(props.mat.bCost);
const setRerender = useState(false)[1];
function rerender(): void {
@ -88,7 +87,6 @@ interface IProps {
// Create a popup that lets the player use the Market TA research for Materials
export function MaterialMarketTaModal(props: IProps): React.ReactElement {
const division = useDivision();
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
@ -102,32 +100,31 @@ export function MaterialMarketTaModal(props: IProps): React.ReactElement {
return (
<Modal open={props.open} onClose={props.onClose}>
{!division.hasResearch("Market-TA.II") && (
<>
<Typography variant="h4">Market-TA.I</Typography>
<Typography>
The maximum sale price you can mark this up to is{" "}
{numeralWrapper.formatMoney(props.mat.bCost + markupLimit)}. This means that if you set the sale price
higher than this, you will begin to experience a loss in number of sales
</Typography>
<>
<Typography variant="h4">Market-TA.I</Typography>
<Typography>
The maximum sale price you can mark this up to is {numeralWrapper.formatMoney(props.mat.bCost + markupLimit)}.
This means that if you set the sale price higher than this, you will begin to experience a loss in number of
sales
</Typography>
<FormControlLabel
control={<Switch checked={props.mat.marketTa1} onChange={onMarketTA1} />}
label={
<Tooltip
title={
<Typography>
If this is enabled, then this Material will automatically be sold at the price identified by
Market-TA.I (i.e. the price shown above)
</Typography>
}
>
<Typography>Use Market-TA.I for Auto-Sale Price</Typography>
</Tooltip>
}
/>
</>
<FormControlLabel
control={<Switch checked={props.mat.marketTa1} onChange={onMarketTA1} />}
label={
<Tooltip
title={
<Typography>
If this is enabled, then this Material will automatically be sold at the price identified by
Market-TA.I (i.e. the price shown above)
</Typography>
}
>
<Typography>Use Market-TA.I for Auto-Sale Price</Typography>
</Tooltip>
}
/>
</>
)}
<MarketTA2 mat={props.mat} />
</Modal>
);

@ -221,7 +221,7 @@ function PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement {
<SellSharesModal open={sellSharesOpen} onClose={() => setSellSharesOpen(false)} rerender={rerender} />
<Tooltip title={<Typography>Buy back shares you that previously issued or sold at market price.</Typography>}>
<span>
<Button disabled={corp.issuedShares > 0.5} onClick={() => setBuybackSharesOpen(true)}>
<Button disabled={corp.issuedShares <1} onClick={() => setBuybackSharesOpen(true)}>
Buyback shares
</Button>
</span>

@ -92,32 +92,31 @@ export function ProductMarketTaModal(props: IProps): React.ReactElement {
return (
<Modal open={props.open} onClose={props.onClose}>
{!division.hasResearch("Market-TA.II") && (
<>
<Typography variant="h4">Market-TA.I</Typography>
<Typography>
The maximum sale price you can mark this up to is{" "}
{numeralWrapper.formatMoney(props.product.pCost + markupLimit)}. This means that if you set the sale price
higher than this, you will begin to experience a loss in number of sales
</Typography>
<>
<Typography variant="h4">Market-TA.I</Typography>
<Typography>
The maximum sale price you can mark this up to is{" "}
{numeralWrapper.formatMoney(props.product.pCost + markupLimit)}. This means that if you set the sale price
higher than this, you will begin to experience a loss in number of sales
</Typography>
<FormControlLabel
control={<Switch checked={props.product.marketTa1} onChange={onChange} />}
label={
<Tooltip
title={
<Typography>
If this is enabled, then this Material will automatically be sold at the price identified by
Market-TA.I (i.e. the price shown above)
</Typography>
}
>
<Typography>Use Market-TA.I for Auto-Sale Price</Typography>
</Tooltip>
}
/>
</>
<FormControlLabel
control={<Switch checked={props.product.marketTa1} onChange={onChange} />}
label={
<Tooltip
title={
<Typography>
If this is enabled, then this Material will automatically be sold at the price identified by
Market-TA.I (i.e. the price shown above)
</Typography>
}
>
<Typography>Use Market-TA.I for Auto-Sale Price</Typography>
</Tooltip>
}
/>
</>
)}
<MarketTA2 product={props.product} />
</Modal>
);

@ -23,7 +23,7 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
The gift is a grid on which you can place upgrades called fragments. The main type of fragment increases a stat,
like your hacking skill or agility exp. Once a stat fragment is placed it then needs to be charged via scripts
in order to become useful. The other kind of fragment is called booster fragments. They increase the efficiency
of the charged happening on fragments neighboring them (no diagonal)
of the charged happening on fragments neighboring them (no diagonal). Q/E to rotate fragments.
</Typography>
{staneksGift.storedCycles > 5 && (
<Typography>

@ -31,6 +31,7 @@ export function determineCrimeSuccess(p: IPlayer, type: string): boolean {
}
export function findCrime(roughName: string): Crime | null {
roughName = roughName.toLowerCase();
if (roughName.includes("shoplift")) {
return Crimes.Shoplift;
} else if (roughName.includes("rob") && roughName.includes("store")) {

@ -1,9 +1,10 @@
import React from "react";
import { DarkWebItems } from "./DarkWebItems";
import { Player } from "../Player";
import { Terminal } from "../Terminal";
import { SpecialServers } from "../Server/data/SpecialServers";
import { numeralWrapper } from "../ui/numeralFormat";
import { Money } from "../ui/React/Money";
//Posts a "help" message if connected to DarkWeb
export function checkIfConnectedToDarkweb(): void {
@ -20,7 +21,11 @@ export function checkIfConnectedToDarkweb(): void {
export function listAllDarkwebItems(): void {
for (const key in DarkWebItems) {
const item = DarkWebItems[key];
Terminal.print(`${item.program} - ${numeralWrapper.formatMoney(item.price)} - ${item.description}`);
Terminal.printRaw(
<>
{item.program} - <Money money={item.price} /> - {item.description}`
</>,
);
}
}
@ -38,7 +43,7 @@ export function buyDarkwebItem(itemName: string): void {
// return if invalid
if (item === null) {
Terminal.print("Unrecognized item: " + itemName);
Terminal.error("Unrecognized item: " + itemName);
return;
}
@ -50,7 +55,7 @@ export function buyDarkwebItem(itemName: string): void {
// return if the player doesn't have enough money
if (Player.money.lt(item.price)) {
Terminal.print("Not enough money to purchase " + item.program);
Terminal.error("Not enough money to purchase " + item.program);
return;
}

@ -16,6 +16,7 @@ export enum Exploit {
Unclickable = "Unclickable",
UndocumentedFunctionCall = "UndocumentedFunctionCall",
TimeCompression = "TimeCompression",
RealityAlteration = "RealityAlteration",
// To the players reading this. Yes you're supposed to add EditSaveFile by
// editing your save file, yes you could add them all, no we don't care
// that's not the point.
@ -31,6 +32,7 @@ const names: {
TimeCompression: "by compressing time",
Unclickable: "by clicking the unclickable.",
UndocumentedFunctionCall: "by looking beyond the documentation.",
RealityAlteration: "by altering reality to suit your whims.",
};
export function ExploitName(exploit: string): string {

@ -42,6 +42,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
const augs: string[] = [];
for (const augName in Augmentations) {
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue;
const aug = Augmentations[augName];
if (!aug.isSpecial) {
augs.push(augName);

@ -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);
}

@ -24,6 +24,7 @@ import { GangMember } from "./GangMember";
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
export class Gang {
facName: string;
@ -115,9 +116,10 @@ export class Gang {
wantedLevelGains += wantedLevelGain;
if (this.members[i].getTask().baseWanted < 0) justice++; // this member is lowering wanted.
}
this.respectGainRate = respectGains;
this.wantedGainRate = wantedLevelGains;
this.moneyGainRate = moneyGains;
const territoryPenalty = (0.2 * this.getTerritory() + 0.8) * BitNodeMultipliers.GangSoftcap;
this.respectGainRate = Math.pow(respectGains, territoryPenalty);
this.wantedGainRate = Math.pow(wantedLevelGains, territoryPenalty);
this.moneyGainRate = Math.pow(moneyGains, territoryPenalty);
const gain = respectGains * numCycles;
this.respect += gain;
// Faction reputation gains is respect gain divided by some constant

@ -63,7 +63,7 @@ export class GangMember {
}
calculateAscensionMult(points: number): number {
return Math.max(Math.pow(points / 4000, 0.7), 1);
return Math.max(Math.pow(points / 2000, 0.7), 1);
}
updateSkillLevels(): void {

@ -6,7 +6,7 @@ export const GangConstants: {
Names: string[];
} = {
// Respect is divided by this to get rep gain
GangRespectToReputationRatio: 25,
GangRespectToReputationRatio: 75,
MaximumGangMembers: 12,
CyclesPerTerritoryAndPowerUpdate: 100,
// Portion of upgrade multiplier that is kept after ascending

@ -53,7 +53,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
params: {
baseRespect: 0.00005,
baseWanted: 0.0001,
baseMoney: 1,
baseMoney: 3,
hackWeight: 100,
difficulty: 1,
},
@ -66,7 +66,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
params: {
baseRespect: 0.00008,
baseWanted: 0.003,
baseMoney: 2.5,
baseMoney: 7.5,
hackWeight: 85,
chaWeight: 15,
difficulty: 3.5,
@ -80,7 +80,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
params: {
baseRespect: 0.0001,
baseWanted: 0.075,
baseMoney: 6,
baseMoney: 18,
hackWeight: 80,
chaWeight: 20,
difficulty: 5,
@ -118,7 +118,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
params: {
baseRespect: 0.0004,
baseWanted: 0.3,
baseMoney: 15,
baseMoney: 45,
hackWeight: 80,
chaWeight: 20,
difficulty: 20,
@ -132,7 +132,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
params: {
baseRespect: 0.001,
baseWanted: 1.25,
baseMoney: 120,
baseMoney: 360,
hackWeight: 75,
chaWeight: 25,
difficulty: 25,
@ -158,7 +158,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
name: "Ethical Hacking",
params: {
baseWanted: -0.001,
baseMoney: 1,
baseMoney: 3,
hackWeight: 90,
chaWeight: 10,
difficulty: 1,
@ -172,7 +172,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
params: {
baseRespect: 0.00005,
baseWanted: 0.00005,
baseMoney: 1.2,
baseMoney: 3.6,
strWeight: 25,
defWeight: 25,
dexWeight: 25,
@ -189,7 +189,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
params: {
baseRespect: 0.00006,
baseWanted: 0.002,
baseMoney: 5,
baseMoney: 15,
agiWeight: 20,
dexWeight: 20,
chaWeight: 60,
@ -209,7 +209,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
params: {
baseRespect: 0.00004,
baseWanted: 0.02,
baseMoney: 2.5,
baseMoney: 7.5,
hackWeight: 10,
strWeight: 25,
defWeight: 25,
@ -232,7 +232,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
params: {
baseRespect: 0.00012,
baseWanted: 0.05,
baseMoney: 15,
baseMoney: 45,
strWeight: 5,
defWeight: 5,
agiWeight: 25,
@ -249,7 +249,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
params: {
baseRespect: 0.00014,
baseWanted: 0.1,
baseMoney: 38,
baseMoney: 114,
hackWeight: 20,
strWeight: 15,
defWeight: 15,
@ -267,7 +267,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
params: {
baseRespect: 0.0002,
baseWanted: 0.24,
baseMoney: 58,
baseMoney: 174,
hackWeight: 15,
strWeight: 20,
defWeight: 20,
@ -289,7 +289,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
params: {
baseRespect: 0.0002,
baseWanted: 0.125,
baseMoney: 24,
baseMoney: 72,
hackWeight: 25,
strWeight: 25,
dexWeight: 25,
@ -305,7 +305,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
params: {
baseRespect: 0.004,
baseWanted: 1.25,
baseMoney: 120,
baseMoney: 360,
hackWeight: 30,
strWeight: 5,
defWeight: 5,

@ -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 {

@ -3,6 +3,7 @@ import { Location } from "../../Locations/Location";
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import { numeralWrapper } from "../../ui/numeralFormat";
interface IProps {
Location: Location;
@ -60,6 +61,21 @@ export function Intro(props: IProps): React.ReactElement {
Maximum level: {props.MaxLevel}
</Typography>
</Grid>
<Grid item xs={10}>
<Typography variant="h5" color="primary">
Difficulty: {numeralWrapper.format(props.Difficulty * 33.3333, "0")} / 100
</Typography>
</Grid>
{props.Difficulty > 1.5 && (
<Grid item xs={10}>
<Typography variant="h5" color="primary">
Warning: This location is too heavily guarded for your current stats, try training or finding an easier
location.
</Typography>
</Grid>
)}
<Grid item xs={10}>
<Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>[{coloredArrow(props.Difficulty)}]</Typography>
<Typography

@ -26,12 +26,12 @@ const difficulties: {
export function SlashGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = { window: 0 };
interpolate(difficulties, props.difficulty, difficulty);
const [guarding, setGuarding] = useState(true);
const [phase, setPhase] = useState(0);
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
if (event.keyCode !== 32) return;
if (guarding) {
if (phase !== 2) {
props.onFailure();
} else {
props.onSuccess();
@ -39,14 +39,15 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
}
useEffect(() => {
let id2 = -1;
const id = window.setTimeout(() => {
setGuarding(false);
id2 = window.setTimeout(() => setGuarding(true), difficulty.window);
let id = window.setTimeout(() => {
setPhase(1);
id = window.setTimeout(() => {
setPhase(2);
id = window.setTimeout(() => setPhase(0), difficulty.window);
}, 250);
}, Math.random() * 3250 + 1500);
return () => {
clearInterval(id);
if (id2 !== -1) clearInterval(id2);
};
}, []);
@ -55,7 +56,9 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
<GameTimer millis={5000} onExpire={props.onFailure} />
<Grid item xs={12}>
<Typography variant="h4">Slash when his guard is down!</Typography>
<Typography variant="h4">{guarding ? "!Guarding!" : "!ATTACKING!"}</Typography>
{phase === 0 && <Typography variant="h4">Guarding ...</Typography>}
{phase === 1 && <Typography variant="h4">Preparing?</Typography>}
{phase === 2 && <Typography variant="h4">ATTACKING!</Typography>}
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>

@ -8,11 +8,12 @@ import { Cities } from "./Cities";
import { IMap } from "../types";
export function createCityMap<T>(initValue: T): IMap<T> {
const map: IMap<any> = {};
const map: IMap<T> = {};
const cities = Object.keys(Cities);
for (let i = 0; i < cities.length; ++i) {
map[cities[i]] = initValue;
}
return map;
// round try JSON so to make sure none of the initial values have the same references.
return JSON.parse(JSON.stringify(map));
}

@ -24,6 +24,7 @@ import { joinFaction } from "../../Faction/FactionHelpers";
import { use } from "../../ui/Context";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { SnackbarEvents } from "../../ui/React/Snackbar";
type IProps = {
loc: Location;
@ -78,7 +79,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>;

@ -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

@ -32,7 +32,7 @@ export interface INetscriptBladeburner {
getTeamSize(type?: any, name?: any): any;
setTeamSize(type?: any, name?: any, size?: any): any;
getCityEstimatedPopulation(cityName: any): any;
getCityEstimatedCommunities(cityName: any): any;
getCityCommunities(cityName: any): any;
getCityChaos(cityName: any): any;
getCity(): any;
switchCity(cityName: any): any;
@ -320,13 +320,13 @@ export function NetscriptBladeburner(
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.cities[cityName].popEst;
},
getCityEstimatedCommunities: function (cityName: any): any {
helper.updateDynamicRam("getCityEstimatedCommunities", getRamCost("bladeburner", "getCityEstimatedCommunities"));
checkBladeburnerAccess("getCityEstimatedCommunities");
checkBladeburnerCity("getCityEstimatedCommunities", cityName);
getCityCommunities: function (cityName: any): any {
helper.updateDynamicRam("getCityCommunities", getRamCost("bladeburner", "getCityCommunities"));
checkBladeburnerAccess("getCityCommunities");
checkBladeburnerCity("getCityCommunities", cityName);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.cities[cityName].commsEst;
return bladeburner.cities[cityName].comms;
},
getCityChaos: function (cityName: any): any {
helper.updateDynamicRam("getCityChaos", getRamCost("bladeburner", "getCityChaos"));

@ -8,6 +8,7 @@ export interface INetscriptExtra {
};
exploit(): void;
bypass(doc: Document): void;
alterReality(): void;
}
export function NetscriptExtra(player: IPlayer, workerScript: WorkerScript): INetscriptExtra {
@ -34,5 +35,13 @@ export function NetscriptExtra(player: IPlayer, workerScript: WorkerScript): INe
doc.completely_unused_field = undefined;
real_document.completely_unused_field = undefined;
},
alterReality: function (): void {
const x = false;
console.warn("I am sure that this variable is false");
if (x !== false) {
console.warn("Reality has been altered!");
player.giveExploit(Exploit.RealityAlteration);
}
},
};
}

@ -540,7 +540,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);
@ -647,9 +647,9 @@ export function runScriptFromScript(
return 0;
}
args = args.map((arg) => {
if (typeof arg === "number") return arg;
return arg + ""; // force cast to string
args.forEach((arg) => {
if (typeof arg !== "string" && typeof arg !== "number")
throw new Error("Only strings and numbers can be passed as arguments to otherscripts.");
});
// Check if the script is already running

@ -2,7 +2,6 @@ import { Factions } from "../../Faction/Factions";
import { Faction } from "../../Faction/Faction";
import { Gang } from "../../Gang/Gang";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { IPlayer } from "../IPlayer";
// Amount of negative karma needed to manage a gang in BitNodes other than 2
@ -16,7 +15,7 @@ export function canAccessGang(this: IPlayer): boolean {
return false;
}
return this.karma <= BitNodeMultipliers.GangKarmaRequirement * GangKarmaRequirement;
return this.karma <= GangKarmaRequirement;
}
export function getGangFaction(this: IPlayer): Faction {

@ -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 */
@ -629,6 +630,7 @@ export function work(this: IPlayer, numCycles: number): boolean {
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.workRepGainRate = this.getWorkRepGain();
this.workMoneyGainRate = this.getWorkMoneyGain();
this.processWorkEarnings(numCycles);
const comp = Companies[this.companyName];
@ -1697,13 +1699,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);
@ -1785,6 +1781,7 @@ export function applyForJob(this: IPlayer, entryPosType: CompanyPosition, sing =
}
this.jobs[company.name] = pos.name;
if (!this.focus && this.isWorking && this.companyName !== this.location) this.resetWorkStatus();
this.companyName = this.location;
if (!sing) {

@ -143,8 +143,8 @@ export class Sleeve extends Person {
* Commit crimes
*/
commitCrime(p: IPlayer, crimeKey: string): boolean {
const crime: Crime | null = Crimes[crimeKey];
if (!(crime instanceof Crime)) {
const crime: Crime | null = Crimes[crimeKey] || Object.values(Crimes).find((crime) => crime.name === crimeKey);
if (!crime) {
return false;
}
@ -164,7 +164,7 @@ export class Sleeve extends Person {
this.currentTaskLocation = String(this.gainRatesForTask.money);
this.crimeType = crimeKey;
this.crimeType = crime.name;
this.currentTaskMaxTime = crime.time;
this.currentTask = SleeveTaskType.Crime;
return true;
@ -179,8 +179,8 @@ export class Sleeve extends Person {
if (this.currentTask === SleeveTaskType.Crime) {
// For crimes, all experience and money is gained at the end
if (this.currentTaskTime >= this.currentTaskMaxTime) {
const crime: Crime | null = Crimes[this.crimeType];
if (!(crime instanceof Crime)) {
const crime: Crime | undefined = Object.values(Crimes).find((crime) => crime.name === this.crimeType);
if (!crime) {
console.error(`Invalid data stored in sleeve.crimeType: ${this.crimeType}`);
this.resetTaskStatus();
return retValue;

@ -104,14 +104,17 @@ export function SleeveElem(props: IProps): React.ReactElement {
);
break;
}
case SleeveTaskType.Crime:
case SleeveTaskType.Crime: {
const crime = Object.values(Crimes).find((crime) => crime.name === props.sleeve.crimeType);
if (!crime) throw new Error("crime should not be undefined");
desc = (
<>
This sleeve is currently attempting to {Crimes[props.sleeve.crimeType].type} (Success Rate:{" "}
{numeralWrapper.formatPercentage(Crimes[props.sleeve.crimeType].successRate(props.sleeve))}).
This sleeve is currently attempting to {crime.type} (Success Rate:{" "}
{numeralWrapper.formatPercentage(crime.successRate(props.sleeve))}).
</>
);
break;
}
case SleeveTaskType.Class:
desc = <>This sleeve is currently studying/taking a course at {props.sleeve.currentTaskLocation}.</>;
break;

@ -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[] = [];

@ -28,11 +28,13 @@ import { dialogBoxCreate } from "./ui/React/DialogBox";
import Decimal from "decimal.js";
import { staneksGift } from "./CotMG/Helper";
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) {
@ -147,10 +149,12 @@ function prestigeAugmentation(): void {
staneksGift.prestigeAugmentation();
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);
@ -280,5 +284,3 @@ function prestigeSourceFile(flume: boolean): void {
resetPidCounter();
}
export { prestigeAugmentation, prestigeSourceFile };

@ -190,7 +190,7 @@ export const programsMetadata: IProgramCreationParams[] = [
time: CONSTANTS.MillisecondsPerQuarterHour,
},
run: (router: IRouter, terminal: ITerminal): void => {
terminal.print("This executable cannot be run.");
terminal.error("This executable cannot be run.");
terminal.print("DeepscanV1.exe lets you run 'scan-analyze' with a depth up to 5.");
},
},
@ -204,7 +204,7 @@ export const programsMetadata: IProgramCreationParams[] = [
time: CONSTANTS.MillisecondsPer2Hours,
},
run: (router: IRouter, terminal: ITerminal): void => {
terminal.print("This executable cannot be run.");
terminal.error("This executable cannot be run.");
terminal.print("DeepscanV2.exe lets you run 'scan-analyze' with a depth up to 10.");
},
},
@ -219,18 +219,18 @@ export const programsMetadata: IProgramCreationParams[] = [
},
run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]): void => {
if (args.length !== 1) {
terminal.print("Must pass a server hostname or IP as an argument for ServerProfiler.exe");
terminal.error("Must pass a server hostname or IP as an argument for ServerProfiler.exe");
return;
}
const targetServer = GetServer(args[0]);
if (targetServer == null) {
terminal.print("Invalid server IP/hostname");
terminal.error("Invalid server IP/hostname");
return;
}
if (!(targetServer instanceof Server)) {
terminal.print(`ServerProfiler.exe can only be run on normal servers.`);
terminal.error(`ServerProfiler.exe can only be run on normal servers.`);
return;
}
@ -268,7 +268,7 @@ export const programsMetadata: IProgramCreationParams[] = [
time: CONSTANTS.MillisecondsPerQuarterHour,
},
run: (router: IRouter, terminal: ITerminal): void => {
terminal.print("This executable cannot be run.");
terminal.error("This executable cannot be run.");
terminal.print("AutoLink.exe lets you automatically connect to other servers when using 'scan-analyze'.");
terminal.print("When using scan-analyze, click on a server's hostname to connect to it.");
},

@ -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 <></>;

@ -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",
},
});
}

@ -28,7 +28,7 @@ export function getPurchaseServerCost(ram: number): number {
return Infinity;
}
const upg = Math.max(0, Math.log(sanitizedRam) / Math.log(2) - 9);
const upg = Math.max(0, Math.log(sanitizedRam) / Math.log(2) - 6);
return (
sanitizedRam *

@ -52,6 +52,8 @@ import { redPillFlag } from "../../RedPill";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
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),
@ -134,10 +136,10 @@ 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.filter((f) => !InvitationsSeen.includes(f)).length;
const programCount = getAvailableCreatePrograms(props.player).length - ProgramsSeen.length;
const canCreateProgram =
programCount > 0 ||
getAvailableCreatePrograms(props.player).length > 0 ||
props.player.augmentations.length > 0 ||
props.player.queuedAugmentations.length > 0 ||
props.player.sourceFiles.length > 0;

@ -1,3 +1,4 @@
import React from "react";
import { TextFile } from "../TextFile";
import { Script } from "../Script/Script";
import { IPlayer } from "../PersonObjects/IPlayer";
@ -18,6 +19,13 @@ export class Output {
}
}
export class RawOutput {
raw: React.ReactNode;
constructor(node: React.ReactNode) {
this.raw = node;
}
}
export class Link {
hostname: string;
dashes: string;
@ -46,7 +54,7 @@ export interface ITerminal {
commandHistory: string[];
commandHistoryIndex: number;
outputHistory: (Output | Link)[];
outputHistory: (Output | Link | RawOutput)[];
// True if a Coding Contract prompt is opened
contractOpen: boolean;
@ -56,6 +64,7 @@ export interface ITerminal {
currDir: string;
print(s: string): void;
printRaw(node: React.ReactNode): void;
error(s: string): void;
clear(): void;

@ -1,4 +1,4 @@
import { ITerminal, Output, Link, TTimer } from "./ITerminal";
import { ITerminal, Output, Link, RawOutput, TTimer } from "./ITerminal";
import { IRouter } from "../ui/Router";
import { IPlayer } from "../PersonObjects/IPlayer";
import { HacknetServer } from "../Hacknet/HacknetServer";
@ -77,7 +77,7 @@ export class Terminal implements ITerminal {
commandHistory: string[] = [];
commandHistoryIndex = 0;
outputHistory: (Output | Link)[] = [new Output(`Bitburner v${CONSTANTS.Version}`, "primary")];
outputHistory: (Output | Link | RawOutput)[] = [new Output(`Bitburner v${CONSTANTS.Version}`, "primary")];
// True if a Coding Contract prompt is opened
contractOpen = false;
@ -93,7 +93,7 @@ export class Terminal implements ITerminal {
TerminalEvents.emit();
}
append(item: Output | Link): void {
append(item: Output | Link | RawOutput): void {
this.outputHistory.push(item);
if (this.outputHistory.length > Settings.MaxTerminalCapacity) {
this.outputHistory.splice(0, this.outputHistory.length - Settings.MaxTerminalCapacity);
@ -105,6 +105,10 @@ export class Terminal implements ITerminal {
this.append(new Output(s, "primary"));
}
printRaw(node: React.ReactNode): void {
this.append(new RawOutput(node));
}
error(s: string): void {
this.append(new Output(s, "error"));
}
@ -428,10 +432,10 @@ export class Terminal implements ITerminal {
case CodingContractResult.Failure:
++contract.tries;
if (contract.tries >= contract.getMaxNumTries()) {
this.print("Contract FAILED - Contract is now self-destructing");
this.error("Contract FAILED - Contract is now self-destructing");
serv.removeContract(contract);
} else {
this.print(`Contract FAILED - ${contract.getMaxNumTries() - contract.tries} tries remaining`);
this.error(`Contract FAILED - ${contract.getMaxNumTries() - contract.tries} tries remaining`);
}
break;
case CodingContractResult.Cancelled:
@ -580,7 +584,7 @@ export class Terminal implements ITerminal {
if (commandArray.length === 1 && commandArray[0] == "help") {
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
@ -588,7 +592,7 @@ export class Terminal implements ITerminal {
if (commandArray.length === 1 && commandArray[0] == "ls") {
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
@ -596,7 +600,7 @@ export class Terminal implements ITerminal {
if (commandArray.length === 1 && commandArray[0] == "scan") {
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
@ -604,7 +608,7 @@ export class Terminal implements ITerminal {
if (commandArray.length == 1 && commandArray[0] == "scan-analyze") {
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
@ -612,7 +616,7 @@ export class Terminal implements ITerminal {
if (commandArray.length == 2 && commandArray[0] == "scan-analyze" && commandArray[1] === 2) {
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
@ -624,11 +628,11 @@ export class Terminal implements ITerminal {
) {
iTutorialNextStep();
} else {
this.print("Wrong command! Try again!");
this.error("Wrong command! Try again!");
return;
}
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
@ -636,7 +640,7 @@ export class Terminal implements ITerminal {
if (commandArray.length === 1 && commandArray[0] === "analyze") {
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
@ -644,7 +648,7 @@ export class Terminal implements ITerminal {
if (commandArray.length == 2 && commandArray[0] == "run" && commandArray[1] == "NUKE.exe") {
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
@ -652,7 +656,7 @@ export class Terminal implements ITerminal {
if (commandArray.length == 1 && commandArray[0] == "hack") {
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
@ -660,7 +664,7 @@ export class Terminal implements ITerminal {
if (commandArray.length == 1 && commandArray[0] == "home") {
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
@ -668,7 +672,7 @@ export class Terminal implements ITerminal {
if (commandArray.length == 2 && commandArray[0] == "nano" && commandArray[1] == "n00dles.script") {
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
@ -676,7 +680,7 @@ export class Terminal implements ITerminal {
if (commandArray.length == 1 && commandArray[0] == "free") {
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
@ -684,7 +688,7 @@ export class Terminal implements ITerminal {
if (commandArray.length == 2 && commandArray[0] == "run" && commandArray[1] == "n00dles.script") {
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
@ -692,12 +696,12 @@ export class Terminal implements ITerminal {
if (commandArray.length == 2 && commandArray[0] == "tail" && commandArray[1] == "n00dles.script") {
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
this.error("Bad command. Please follow the tutorial");
return;
}
break;
default:
this.print("Please follow the tutorial, or click 'EXIT' if you'd like to skip it");
this.error("Please follow the tutorial, or click 'EXIT' if you'd like to skip it");
return;
}
}

@ -11,7 +11,7 @@ export function analyze(
args: (string | number)[],
): void {
if (args.length !== 0) {
terminal.print("Incorrect usage of analyze command. Usage: analyze");
terminal.error("Incorrect usage of analyze command. Usage: analyze");
return;
}
terminal.startAnalyze();

@ -13,7 +13,7 @@ export function backdoor(
args: (string | number)[],
): void {
if (args.length !== 0) {
terminal.print("Incorrect usage of backdoor command. Usage: backdoor");
terminal.error("Incorrect usage of backdoor command. Usage: backdoor");
return;
}

@ -24,7 +24,7 @@ export function kill(
if (res) {
terminal.print(`Killing script with PID ${pid}`);
} else {
terminal.print(`Failed to kill script with PID ${pid}. No such script exists`);
terminal.error(`Failed to kill script with PID ${pid}. No such script exists`);
}
return;

@ -1,8 +1,10 @@
import React from "react";
import { ITerminal } from "../ITerminal";
import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { getFirstParentDirectory, isValidDirectoryPath, evaluateDirectoryPath } from "../../Terminal/DirectoryHelpers";
import Typography from "@mui/material/Typography";
export function ls(
terminal: ITerminal,
@ -112,7 +114,7 @@ export function ls(
allMessages.sort();
folders.sort();
function postSegments(segments: string[]): void {
function postSegments(segments: string[], style?: any): void {
const maxLength = Math.max(...segments.map((s) => s.length)) + 1;
const filesPerRow = Math.floor(80 / maxLength);
for (let i = 0; i < segments.length; i++) {
@ -124,23 +126,27 @@ export function ls(
i++;
}
i--;
terminal.print(row);
if (!style) {
terminal.print(row);
} else {
terminal.printRaw(<span style={style}>{row}</span>);
}
}
}
const groups = [
{ segments: folders },
{ segments: folders, style: { color: "cyan" } },
{ segments: allMessages },
{ segments: allTextFiles },
{ segments: allPrograms },
{ segments: allContracts },
{ segments: allScripts },
{ segments: allScripts, style: { color: "yellow", fontStyle: "bold" } },
].filter((g) => g.segments.length > 0);
for (let i = 0; i < groups.length; i++) {
if (i !== 0) {
terminal.print("");
terminal.print("");
}
postSegments(groups[i].segments);
postSegments(groups[i].segments, groups[i].style);
}
}

@ -41,5 +41,5 @@ export function runProgram(
}
}
terminal.print("Invalid executable. Cannot be run");
terminal.error("Invalid executable. Cannot be run");
}

@ -41,7 +41,7 @@ export function runScript(
// Check if this script is already running
if (findRunningScript(scriptName, args, server) != null) {
terminal.print("ERROR: This script is already running. Cannot run multiple instances");
terminal.error("This script is already running. Cannot run multiple instances");
return;
}
@ -56,12 +56,12 @@ export function runScript(
const ramAvailable = server.maxRam - server.ramUsed;
if (!server.hasAdminRights) {
terminal.print("Need root access to run script");
terminal.error("Need root access to run script");
return;
}
if (ramUsage > ramAvailable) {
terminal.print(
terminal.error(
"This machine does not have enough RAM to run this script with " +
numThreads +
" threads. Script requires " +
@ -90,5 +90,5 @@ export function runScript(
return;
}
terminal.print("ERROR: No such script");
terminal.error("No such script");
}

@ -19,7 +19,7 @@ export function wget(
const url = args[0] + "";
const target = terminal.getFilepath(args[1] + "");
if (!isScriptFilename(target) && !target.endsWith(".txt")) {
return terminal.print(`wget failed: Invalid target file. Target file must be script or text file`);
return terminal.error(`wget failed: Invalid target file. Target file must be script or text file`);
}
$.get(
url,
@ -31,7 +31,7 @@ export function wget(
res = server.writeToTextFile(target, data);
}
if (!res.success) {
return terminal.print("wget failed");
return terminal.error("wget failed");
}
if (res.overwritten) {
return terminal.print(`wget successfully retrieved content and overwrote ${target}`);

@ -7,7 +7,7 @@ import { Theme } from "@mui/material/styles";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
import Box from "@mui/material/Box";
import { ITerminal, Output, Link } from "../ITerminal";
import { ITerminal, Output, Link, RawOutput } from "../ITerminal";
import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { TerminalInput } from "./TerminalInput";
@ -94,6 +94,14 @@ export function TerminalRoot({ terminal, router, player }: IProps): React.ReactE
</Typography>
</ListItem>
);
if (item instanceof RawOutput)
return (
<ListItem key={i} classes={{ root: classes.nopadding }}>
<Typography classes={{ root: classes.preformatted }} paragraph={false}>
{item.raw}
</Typography>
</ListItem>
);
if (item instanceof Link)
return (
<ListItem key={i} classes={{ root: classes.nopadding }}>

@ -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} />
))}
</>
);
}

@ -76,6 +76,8 @@ import { InvitationModal } from "../Faction/ui/InvitationModal";
import { enterBitNode } from "../RedPill";
import { Context } from "./Context";
const htmlLocation = location;
interface IProps {
terminal: ITerminal;
player: IPlayer;
@ -215,6 +217,14 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
return ITutorialEvents.subscribe(rerender);
}, []);
function killAllScripts(): void {
for (const server of GetAllServers()) {
server.runningScripts = [];
}
saveObject.saveGame();
setTimeout(() => htmlLocation.reload(), 2000);
}
Router = {
page: () => page,
toActiveScripts: () => setPage(Page.ActiveScripts),
@ -288,7 +298,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
<Context.Router.Provider value={Router}>
<Overview>
{!ITutorial.isRunning ? (
<CharacterOverview save={() => saveObject.saveGame()} />
<CharacterOverview save={() => saveObject.saveGame()} killScripts={killAllScripts} />
) : (
<InteractiveTutorialRoot />
)}
@ -372,12 +382,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
player={player}
save={() => saveObject.saveGame()}
export={() => saveObject.exportGame()}
forceKill={() => {
for (const server of GetAllServers()) {
server.runningScripts = [];
}
dialogBoxCreate("Forcefully deleted all running scripts. Please save and refresh page.");
}}
forceKill={killAllScripts}
softReset={() => {
dialogBoxCreate("Soft Reset!");
prestigeAugmentation();

@ -265,7 +265,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
content: (
<>
<Typography>You now have root access! You can hack the server using </Typography>
<Typography classes={{ root: classes.textfield }}>{"[home ~/]> hack"}</Typography>
<Typography classes={{ root: classes.textfield }}>{"[n00dles ~/]> hack"}</Typography>
<Typography> Try doing that now.</Typography>
</>
@ -296,7 +296,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
content: (
<>
<Typography>From any server you can get back home using</Typography>
<Typography classes={{ root: classes.textfield }}>{"[home ~/]> home"}</Typography>
<Typography classes={{ root: classes.textfield }}>{"[n00dles ~/]> home"}</Typography>
<Typography>Let's head home before creating our first script!</Typography>
</>
@ -468,7 +468,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
[iTutorialSteps.HacknetNodesIntroduction as number]: {
content: (
<Typography>
here you can purchase new Hacknet Nodes and upgrade your existing ones. Let's purchase a new one now.
Here you can purchase new Hacknet Nodes and upgrade your existing ones. Let's purchase a new one now.
</Typography>
),
canNext: true,

@ -6,23 +6,24 @@ import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Reputation } from "./Reputation";
import { KillScriptsModal } from "./KillScriptsModal";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow";
import Paper from "@mui/material/Paper";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import IconButton from "@mui/material/IconButton";
import SaveIcon from "@mui/icons-material/Save";
import ClearAllIcon from "@mui/icons-material/ClearAll";
import { Settings } from "../../Settings/Settings";
import { use } from "../Context";
interface IProps {
save: () => void;
killScripts: () => void;
}
function Intelligence(): React.ReactElement {
@ -138,7 +139,8 @@ const useStyles = makeStyles((theme: Theme) =>
}),
);
export function CharacterOverview({ save }: IProps): React.ReactElement {
export function CharacterOverview({ save, killScripts }: IProps): React.ReactElement {
const [killOpen, setKillOpen] = useState(false);
const player = use.Player();
const setRerender = useState(false)[1];
@ -150,162 +152,162 @@ export function CharacterOverview({ save }: IProps): React.ReactElement {
const classes = useStyles();
return (
<Paper square>
<Box m={1}>
<Table size="small">
<TableBody>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.hp }}>HP&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.hp }}>
{numeralWrapper.formatHp(player.hp)}&nbsp;/&nbsp;{numeralWrapper.formatHp(player.max_hp)}
</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography id="overview-hp-hook" classes={{ root: classes.hp }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<>
<Table sx={{ display: "block", m: 1 }}>
<TableBody>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.hp }}>HP&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.hp }}>
{numeralWrapper.formatHp(player.hp)}&nbsp;/&nbsp;{numeralWrapper.formatHp(player.max_hp)}
</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography id="overview-hp-hook" classes={{ root: classes.hp }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.money }}>Money&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.money }}>
{numeralWrapper.formatMoney(player.money.toNumber())}
</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography id="overview-money-hook" classes={{ root: classes.money }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.money }}>Money&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.money }}>
{numeralWrapper.formatMoney(player.money.toNumber())}
</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography id="overview-money-hook" classes={{ root: classes.money }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.hack }}>Hack&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.hack }}>
{numeralWrapper.formatSkill(player.hacking_skill)}
</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cell }}>
<Typography id="overview-hack-hook" classes={{ root: classes.hack }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.hack }}>Hack&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.hack }}>
{numeralWrapper.formatSkill(player.hacking_skill)}
</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cell }}>
<Typography id="overview-hack-hook" classes={{ root: classes.hack }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>Str&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>
{numeralWrapper.formatSkill(player.strength)}
</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography id="overview-str-hook" classes={{ root: classes.combat }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>Str&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>{numeralWrapper.formatSkill(player.strength)}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography id="overview-str-hook" classes={{ root: classes.combat }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>Def&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>{numeralWrapper.formatSkill(player.defense)}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography id="overview-def-hook" classes={{ root: classes.combat }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>Def&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>{numeralWrapper.formatSkill(player.defense)}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography id="overview-def-hook" classes={{ root: classes.combat }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>Dex&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>
{numeralWrapper.formatSkill(player.dexterity)}
</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography id="overview-dex-hook" classes={{ root: classes.combat }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.combat }}>Agi&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.combat }}>{numeralWrapper.formatSkill(player.agility)}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cell }}>
<Typography id="overview-agi-hook" classes={{ root: classes.combat }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>Dex&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>{numeralWrapper.formatSkill(player.dexterity)}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography id="overview-dex-hook" classes={{ root: classes.combat }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.combat }}>Agi&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.combat }}>{numeralWrapper.formatSkill(player.agility)}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cell }}>
<Typography id="overview-agi-hook" classes={{ root: classes.combat }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.cha }}>Cha&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.cha }}>{numeralWrapper.formatSkill(player.charisma)}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography id="overview-cha-hook" classes={{ root: classes.cha }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<Intelligence />
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.cha }}>Cha&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.cha }}>{numeralWrapper.formatSkill(player.charisma)}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography id="overview-cha-hook" classes={{ root: classes.cha }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<Intelligence />
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography id="overview-extra-hook-0" classes={{ root: classes.hack }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
<TableCell component="th" scope="row" align="right" classes={{ root: classes.cell }}>
<Typography id="overview-extra-hook-1" classes={{ root: classes.hack }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
<TableCell component="th" scope="row" align="right" classes={{ root: classes.cell }}>
<Typography id="overview-extra-hook-2" classes={{ root: classes.hack }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<Work />
<Bladeburner />
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography id="overview-extra-hook-0" classes={{ root: classes.hack }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
<TableCell component="th" scope="row" align="right" classes={{ root: classes.cell }}>
<Typography id="overview-extra-hook-1" classes={{ root: classes.hack }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
<TableCell component="th" scope="row" align="right" classes={{ root: classes.cell }}>
<Typography id="overview-extra-hook-2" classes={{ root: classes.hack }}>
{/*Hook for player scripts*/}
</Typography>
</TableCell>
</TableRow>
<Work />
<Bladeburner />
<TableRow>
<TableCell align="center" colSpan={2} classes={{ root: classes.cellNone }}>
<IconButton onClick={save}>
<SaveIcon color={Settings.AutosaveInterval !== 0 ? "primary" : "error"} />
</IconButton>
</TableCell>
</TableRow>
</TableBody>
</Table>
</Box>
</Paper>
<TableRow>
<TableCell align="center" classes={{ root: classes.cellNone }}>
<IconButton onClick={save}>
<SaveIcon color={Settings.AutosaveInterval !== 0 ? "primary" : "error"} />
</IconButton>
</TableCell>
<TableCell align="center" classes={{ root: classes.cellNone }}>
<IconButton onClick={() => setKillOpen(true)}>
<ClearAllIcon color="error" />
</IconButton>
</TableCell>
</TableRow>
</TableBody>
</Table>
<KillScriptsModal open={killOpen} onClose={() => setKillOpen(false)} killScripts={killScripts} />
</>
);
}

@ -0,0 +1,24 @@
import React from "react";
import { Modal } from "./Modal";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
interface IProps {
open: boolean;
onClose: () => void;
killScripts: () => void;
}
export function KillScriptsModal(props: IProps): React.ReactElement {
function onClick(): void {
props.killScripts();
props.onClose();
}
return (
<Modal open={props.open} onClose={props.onClose}>
<Typography>Forcefully kill all running scripts? This will also save your game and reload the game.</Typography>
<Button onClick={onClick}>KILL</Button>
</Modal>
);
}

@ -1,17 +1,29 @@
import React, { useState } from "react";
import makeStyles from "@mui/styles/makeStyles";
import Box from "@mui/material/Box";
import Collapse from "@mui/material/Collapse";
import Fab from "@mui/material/Fab";
import Paper from "@mui/material/Paper";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import VisibilityIcon from "@mui/icons-material/Visibility";
import { use } from "../Context";
import { Page } from "../Router";
const useStyles = makeStyles({
nobackground: {
backgroundColor: "#0000",
visibilityToggle: {
backgroundColor: "transparent",
position: "absolute",
top: "100%",
right: 0,
},
overviewContainer: {
position: "fixed",
top: 0,
right: 0,
zIndex: 1500,
display: "flex",
justifyContent: "flex-end",
flexDirection: "column",
},
});
@ -30,16 +42,13 @@ export function Overview({ children }: IProps): React.ReactElement {
} else {
icon = <VisibilityIcon color="primary" />;
}
return (
<div style={{ position: "fixed", top: 0, right: 0, zIndex: 1500 }}>
<Box display="flex" justifyContent="flex-end" flexDirection={"column"}>
<Collapse in={open}>{children}</Collapse>
<Box display="flex" justifyContent="flex-end">
<Fab classes={{ root: classes.nobackground }} onClick={() => setOpen((old) => !old)}>
{icon}
</Fab>
</Box>
</Box>
</div>
<Paper square classes={{ root: classes.overviewContainer }}>
<Collapse in={open}>{children}</Collapse>
<Fab size="small" classes={{ root: classes.visibilityToggle }} onClick={() => setOpen((old) => !old)}>
{icon}
</Fab>
</Paper>
);
}
}

@ -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 {