Merge branch 'dev' of github.com:danielyxie/bitburner into dev

This commit is contained in:
Olivier Gagnon 2021-12-29 02:04:28 -05:00
commit 04f2cfe522
25 changed files with 200 additions and 110 deletions

@ -118,7 +118,7 @@ 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.
After that you can open any browser and navigate to `localhost:8000` and play the game.
Saving a file will reload the game automatically.
#### Submitting a Pull Request

@ -432,7 +432,10 @@ empty file will be created.
ps
^^
$ ps [-g, --grep pattern]
Prints all scripts that are currently running on the current server.
The :code:`-g, --grep pattern` option will only output running scripts where the name matches the provided pattern.
rm
^^

@ -18,7 +18,7 @@
| [CharacterInfo](./bitburner.characterinfo.md) | |
| [CharacterMult](./bitburner.charactermult.md) | |
| [CodingAttemptOptions](./bitburner.codingattemptoptions.md) | Options to affect the behavior of [CodingContract](./bitburner.codingcontract.md) attempt. |
| [CodingContract](./bitburner.codingcontract.md) | Coding Contact API |
| [CodingContract](./bitburner.codingcontract.md) | Coding Contract API |
| [Corporation](./bitburner.corporation.md) | Corporation API |
| [CorporationInfo](./bitburner.corporationinfo.md) | General info about a corporation |
| [CrimeStats](./bitburner.crimestats.md) | Data representing the internal values of a crime. |

@ -28,5 +28,5 @@ Returns the amount of time in milliseconds it takes to execute the grow Netscrip
RAM cost: 0.05 GB
Returns the amount of time in milliseconds it takes to execute the grow Netscript function on the target server. The function takes in an optional hackLvl parameter that can be specified to see what the grow time would be at different hacking levels.
Returns the amount of time in milliseconds it takes to execute the grow Netscript function on the target server.

@ -28,5 +28,5 @@ Returns the amount of time in milliseconds it takes to execute the hack Netscrip
RAM cost: 0.05 GB
Returns the amount of time in milliseconds it takes to execute the hack Netscript function on the target server. The function takes in an optional hackLvl parameter that can be specified to see what the hack time would be at different hacking levels.
Returns the amount of time in milliseconds it takes to execute the hack Netscript function on the target server.

@ -28,5 +28,5 @@ Returns the amount of time in milliseconds it takes to execute the grow Netscrip
RAM cost: 0.05 GB
Returns the amount of time in milliseconds it takes to execute the weaken() Netscript function on the target server. The function takes in an optional hackLvl parameter that can be specified to see what the weaken time would be at different hacking levels.
Returns the amount of time in milliseconds it takes to execute the weaken() Netscript function on the target server.

@ -12,6 +12,7 @@
"dependencies": {
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@material-ui/core": "^4.12.3",
"@microsoft/api-documenter": "^7.13.65",
"@microsoft/api-extractor": "^7.18.17",
"@monaco-editor/react": "^4.2.2",
@ -37,9 +38,11 @@
"jquery": "^3.5.0",
"jszip": "^3.7.0",
"material-ui-color": "^1.2.0",
"material-ui-popup-state": "^1.5.3",
"monaco-editor": "^0.27.0",
"notistack": "^2.0.2",
"numeral": "2.0.6",
"prop-types": "^15.8.0",
"raw-loader": "^4.0.2",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.0",

@ -36,7 +36,7 @@ export function printAliases(): void {
// Returns true if successful, false otherwise
export function parseAliasDeclaration(dec: string, global = false): boolean {
const re = /^([\w|!|%|,|@|-]+)="(.+)"$/;
const re = /^([\w|!|%|,|@|-]+)=(("(.+)")|('(.+)'))$/;
const matches = dec.match(re);
if (matches == null || matches.length != 3) {
return false;

@ -4,6 +4,7 @@ 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";
import { DarkWebItem } from "./DarkWebItem";
@ -13,8 +14,8 @@ export function checkIfConnectedToDarkweb(): void {
if (server !== null && SpecialServers.DarkWeb == server.hostname) {
Terminal.print(
"You are now connected to the dark web. From the dark web you can purchase illegal items. " +
"Use the 'buy -l' command to display a list of all the items you can buy. Use 'buy [item-name] " +
"to purchase an item.",
"Use the 'buy -l' command to display a list of all the items you can buy. Use 'buy [item-name]' " +
"to purchase an item. Use the 'buy -a' command to purchase unowned all items.",
);
}
}
@ -87,3 +88,30 @@ export function buyDarkwebItem(itemName: string): void {
"You have purchased the " + item.program + " program. The new program can be found on your home computer.",
);
}
export function buyAllDarkwebItems(): void {
const itemsToBuy: DarkWebItem[] = [];
let cost = 0;
for (const key in DarkWebItems) {
const item = DarkWebItems[key];
if (!Player.hasProgram(item.program)) {
itemsToBuy.push(item);
cost += item.price;
}
}
if (itemsToBuy.length === 0) {
Terminal.print("All available programs have been purchased already.");
return;
}
if (cost > Player.money) {
Terminal.error("Not enough money to purchase remaining programs, " + numeralWrapper.formatMoney(cost) + " required");
return;
}
for (const item of itemsToBuy) {
buyDarkwebItem(item.program);
}
}

@ -952,7 +952,6 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return runScriptFromScript("run", scriptServer, scriptname, args, workerScript, threads);
},
exec: function (scriptname: any, hostname: any, threads: any = 1, ...args: any[]): any {
console.log(`${scriptname} ${hostname} ${threads} ${JSON.stringify(args)}`);
updateDynamicRam("exec", getRamCost("exec"));
if (scriptname === undefined || hostname === undefined) {
throw makeRuntimeErrorMsg("exec", "Usage: exec(scriptname, server, [numThreads], [arg1], [arg2]...)");

@ -496,7 +496,7 @@ export function NetscriptCorporation(
shareSaleCooldown: corporation.shareSaleCooldown,
issuedShares: corporation.issuedShares,
sharePrice: corporation.sharePrice,
state: corporation.state + "",
state: corporation.state.getState(),
};
},
};

@ -81,7 +81,7 @@ function startNetscript2Script(workerScript: WorkerScript): Promise<WorkerScript
if (propName === "asleep") return f(...args); // OK for multiple simultaneous calls to sleep.
const msg =
"Concurrent calls to Netscript functions not allowed! " +
"Concurrent calls to Netscript functions are not allowed! " +
"Did you forget to await hack(), grow(), or some other " +
"promise-returning function? (Currently running: %s tried to run: %s)";
if (runningFn) {

@ -654,6 +654,8 @@ export function finishWork(this: IPlayer, cancelled: boolean, sing = false): str
this.workRepGained *= this.cancelationPenalty();
}
const penaltyString = this.cancelationPenalty() === 0.5 ? "half" : "three-quarters";
const company = Companies[this.companyName];
company.playerReputation += this.workRepGained;
@ -665,12 +667,12 @@ export function finishWork(this: IPlayer, cancelled: boolean, sing = false): str
<Money money={this.workMoneyGained} />
<br />
<Reputation reputation={this.workRepGained} /> reputation for the company <br />
{numeralWrapper.formatExp(this.workHackExpGained)} hacking exp <br />
{numeralWrapper.formatExp(this.workStrExpGained)} strength exp <br />
{numeralWrapper.formatExp(this.workDefExpGained)} defense exp <br />
{numeralWrapper.formatExp(this.workDexExpGained)} dexterity exp <br />
{numeralWrapper.formatExp(this.workAgiExpGained)} agility exp <br />
{numeralWrapper.formatExp(this.workChaExpGained)} charisma exp
{this.workHackExpGained > 0 && <>{numeralWrapper.formatExp(this.workHackExpGained)} hacking exp <br /></>}
{this.workStrExpGained > 0 && <>{numeralWrapper.formatExp(this.workStrExpGained)} strength exp <br /></>}
{this.workDefExpGained > 0 && <>{numeralWrapper.formatExp(this.workDefExpGained)} defense exp <br /></>}
{this.workDexExpGained > 0 && <>{numeralWrapper.formatExp(this.workDexExpGained)} dexterity exp <br /></>}
{this.workAgiExpGained > 0 && <>{numeralWrapper.formatExp(this.workAgiExpGained)} agility exp <br /></>}
{this.workChaExpGained > 0 && <>{numeralWrapper.formatExp(this.workChaExpGained)} charisma exp <br /></>}
<br />
</>
);
@ -680,7 +682,7 @@ export function finishWork(this: IPlayer, cancelled: boolean, sing = false): str
<>
You worked a short shift of {convertTimeMsToTimeElapsedString(this.timeWorked)} <br />
<br />
Since you cancelled your work early, you only gained half of the reputation you earned. <br />
Since you cancelled your work early, you only gained {penaltyString} of the reputation you earned. <br />
<br />
{content}
</>

@ -17,6 +17,17 @@ export function calculateSkillProgress(exp: number, mult = 1): ISkillProgress {
if (nextExperience < 0) nextExperience = 0;
const normalize = (value: number): number => ((value - baseExperience) * 100) / (nextExperience - baseExperience);
let progress = (nextExperience - baseExperience !== 0) ? normalize(exp) : 99.99;
// Clamp progress: When sleeves are working out, the player gets way too much progress
if (progress < 0) progress = 0
if (progress > 100) progress = 100;
// Clamp floating point imprecisions
let currentExperience = exp - baseExperience;
let remainingExperience = nextExperience - exp;
if (currentExperience < 0) currentExperience = 0;
if (remainingExperience < 0) remainingExperience = 0;
return {
currentSkill,
@ -24,7 +35,9 @@ export function calculateSkillProgress(exp: number, mult = 1): ISkillProgress {
baseExperience,
experience: exp,
nextExperience,
progress: (nextExperience - baseExperience !== 0) ? normalize(exp) : 99.99
currentExperience,
remainingExperience,
progress
}
}
@ -34,6 +47,8 @@ export interface ISkillProgress {
baseExperience: number;
experience: number;
nextExperience: number;
currentExperience: number;
remainingExperience: number;
progress: number;
}
@ -41,6 +56,7 @@ export function getEmptySkillProgress(): ISkillProgress {
return {
currentSkill: 0, nextSkill: 0,
baseExperience: 0, experience: 0, nextExperience: 0,
currentExperience: 0, remainingExperience: 0,
progress: 0,
};
}

@ -1045,7 +1045,7 @@ export interface TIX {
getPurchaseCost(sym: string, shares: number, posType: string): number;
/**
* Calculate profit of setting stocks.
* Calculate profit of selling stocks.
* @remarks
* RAM cost: 2 GB
* Calculates and returns how much you would gain from selling a given number of shares of a stock.
@ -2890,7 +2890,7 @@ export interface Bladeburner {
}
/**
* Coding Contact API
* Coding Contract API
* @public
*/
export interface CodingContract {
@ -4690,11 +4690,12 @@ export interface NS extends Singularity {
* Kills all running scripts on the specified server. This function returns true
* if any scripts were killed, and false otherwise. In other words, it will return
* true if there are any scripts running on the target server.
* If no host is defined, it will kill all scripts, where the script is running.
*
* @param host - IP or hostname of the server on which to kill all scripts.
* @returns True if any scripts were killed, and false otherwise.
*/
killall(host: string): boolean;
killall(host?: string): boolean;
/**
* Terminates the current script immediately.
@ -5486,8 +5487,6 @@ export interface NS extends Singularity {
* The required time is increased by the security level of the target server and decreased by the player's hacking level.
*
* @param host - Host of target server.
* @param hackLvl - Optional hacking level for the calculation. Defaults to players current hacking level.
* @param intLvl - Optional intelligence level for the calculation. Defaults to players current intelligence level. (Intelligence is unlocked after obtaining Source-File 5).
* @returns Returns the amount of time in milliseconds it takes to execute the hack Netscript function. Returns Infinity if called on a Hacknet Server.
*/
getHackTime(host: string): number;
@ -5502,8 +5501,6 @@ export interface NS extends Singularity {
* The required time is increased by the security level of the target server and decreased by the player's hacking level.
*
* @param host - Host of target server.
* @param hackLvl - Optional hacking level for the calculation. Defaults to players current hacking level.
* @param intLvl - Optional intelligence level for the calculation. Defaults to players current intelligence level. (Intelligence is unlocked after obtaining Source-File 5).
* @returns Returns the amount of time in milliseconds it takes to execute the grow Netscript function. Returns Infinity if called on a Hacknet Server.
*/
getGrowTime(host: string): number;
@ -5518,8 +5515,6 @@ export interface NS extends Singularity {
* The required time is increased by the security level of the target server and decreased by the player's hacking level.
*
* @param host - Host of target server.
* @param hackLvl - Optional hacking level for the calculation. Defaults to players current hacking level.
* @param intLvl - Optional intelligence level for the calculation. Defaults to players current intelligence level. (Intelligence is unlocked after obtaining Source-File 5).
* @returns Returns the amount of time in milliseconds it takes to execute the grow Netscript function. Returns Infinity if called on a Hacknet Server.
*/
getWeakenTime(host: string): number;
@ -6127,7 +6122,7 @@ interface CorporationInfo {
issuedShares: number;
/** Price of the shares */
sharePrice: number;
/** State of the corporation, like PRODUCTION or EXPORT */
/** State of the corporation. Possible states are START, PURCHASE, PRODUCTION, SALE, EXPORT. */
state: string;
}

@ -273,7 +273,7 @@ export function SidebarRoot(props: IProps): React.ReactElement {
// Alt-o - Options
function handleShortcuts(this: Document, event: KeyboardEvent): any {
if (Settings.DisableHotkeys) return;
if (props.player.isWorking || redPillFlag) return;
if ((props.player.isWorking && props.player.focus) || redPillFlag) return;
if (event.keyCode == KEY.T && event.altKey) {
event.preventDefault();
clickTerminal();

@ -27,7 +27,7 @@ export const TerminalHelpText: string[] = [
"killall Stops all running scripts on the current machine",
"ls [dir] [| grep pattern] Displays all files on the machine",
"lscpu Displays the number of CPU cores on the machine",
"mem [script] [-t] [n] Displays the amount of RAM required to run the script",
"mem [script] [-t n] Displays the amount of RAM required to run the script",
"mv [src] [dest] Move/rename a text or script file",
"nano [file ...] Text editor - Open up and edit one or more scripts or text files",
"ps Display all scripts that are currently running",
@ -35,7 +35,7 @@ export const TerminalHelpText: string[] = [
"run [name] [-t n] [--tail] [args...] Execute a program or script",
"scan Prints all immediately-available network connections",
"scan-analyze [d] [-a] Prints info for all servers up to <i>d</i> nodes away",
"scp [file] [server] Copies a file to a destination server",
"scp [file ...] [server] Copies a file to a destination server",
"sudov Shows whether you have root access on this computer",
"tail [script] [args...] Displays dynamic logs for the specified script",
"top Displays all running scripts and their RAM usage",
@ -80,7 +80,7 @@ export const HelpTexts: IMap<string[]> = {
" ",
],
analyze: [
"analze",
"analyze",
" ",
"Prints details and statistics about the current server. The information that is printed includes basic ",
"server details such as the hostname, whether the player has root access, what ports are opened/closed, and also ",
@ -96,13 +96,15 @@ export const HelpTexts: IMap<string[]> = {
" ",
],
buy: [
"buy [-l / program]",
"buy [-l / -a / program]",
" ",
"Purchase a program through the Dark Web. Requires a TOR router to use.",
" ",
"If this command is ran with the '-l' flag, it will display a list of all programs that can be bought through the ",
"dark web to the Terminal, as well as their costs.",
" ",
"If this command is ran with the '-a' flag, it will attempt to purchase all unowned programs.",
" ",
"Otherwise, the name of the program must be passed in as a parameter. This name is NOT case-sensitive.",
],
cat: [
@ -191,7 +193,7 @@ export const HelpTexts: IMap<string[]> = {
free: [
"free",
" ",
"Display's the memory usage on the current machine. Print the amount of RAM that is available on the current server as well as ",
"Displays the memory usage on the current machine. Print the amount of RAM that is available on the current server as well as ",
"how much of it is being used.",
],
grow: [
@ -360,12 +362,17 @@ export const HelpTexts: IMap<string[]> = {
"-a flag at the end of the command if you would like to enable that.",
],
scp: [
"scp [filename] [target server]",
"scp [filename ...] [target server]",
" ",
"Copies the specified file from the current server to the target server. ",
"This command only works for script files (.script extension), literature files (.lit extension), ",
"Copies the specified file(s) from the current server to the target server. ",
"This command only works for script files (.script or .js extension), literature files (.lit extension), ",
"and text files (.txt extension). ",
"The second argument passed in must be the hostname or IP of the target server.",
"The second argument passed in must be the hostname or IP of the target server. Examples:",
" ",
"scp foo.script n00dles",
" ",
"scp foo.script bar.script n00dles",
" ",
],
sudov: ["sudov", " ", "Prints whether or not you have root access to the current machine"],

@ -2,7 +2,7 @@ import { ITerminal } from "../ITerminal";
import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { listAllDarkwebItems, buyDarkwebItem } from "../../DarkWeb/DarkWeb";
import { listAllDarkwebItems, buyAllDarkwebItems, buyDarkwebItem } from "../../DarkWeb/DarkWeb";
import { SpecialServers } from "../../Server/data/SpecialServers";
import { GetServer } from "../../Server/AllServers";
@ -22,12 +22,15 @@ export function buy(
if (args.length != 1) {
terminal.print("Incorrect number of arguments. Usage: ");
terminal.print("buy -l");
terminal.print("buy -a");
terminal.print("buy [item name]");
return;
}
const arg = args[0] + "";
if (arg == "-l" || arg == "-1" || arg == "--list") {
listAllDarkwebItems();
} else if (arg == "-a" || arg == "--all") {
buyAllDarkwebItems();
} else {
buyDarkwebItem(arg);
}

@ -53,7 +53,7 @@ export function mv(
// Also, you can't convert between different file types
if (isScriptFilename(source)) {
const script = srcFile as Script;
if (!isScriptFilename(dest)) {
if (!isScriptFilename(destPath)) {
terminal.error(`Source and destination files must have the same type`);
return;
}
@ -66,7 +66,7 @@ export function mv(
if (destFile != null) {
// Already exists, will be overwritten, so we'll delete it
const status = server.removeFile(dest);
const status = server.removeFile(destPath);
if (!status.res) {
terminal.error(`Something went wrong...please contact game dev (probably a bug)`);
return;

@ -2,6 +2,7 @@ import { ITerminal } from "../ITerminal";
import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import * as libarg from "arg"
export function ps(
terminal: ITerminal,
@ -10,16 +11,40 @@ export function ps(
server: BaseServer,
args: (string | number | boolean)[],
): void {
if (args.length !== 0) {
terminal.error("Incorrect usage of ps command. Usage: ps");
let flags;
try{
flags = libarg({
'--grep': String,
'-g': '--grep'
},
{ argv: args }
)
}catch(e){
// catch passing only -g / --grep with no string to use as the search
terminal.error("Incorrect usage of ps command. Usage: ps [-g, --grep pattern]");
return;
}
for (let i = 0; i < server.runningScripts.length; i++) {
const rsObj = server.runningScripts[i];
let res = `(PID - ${rsObj.pid}) ${rsObj.filename}`;
for (let j = 0; j < rsObj.args.length; ++j) {
res += " " + rsObj.args[j].toString();
const pattern = flags['--grep']
if (pattern) {
const re = new RegExp(pattern.toString())
const matching = server.runningScripts.filter((x) => re.test(x.filename))
for (let i = 0; i < matching.length; i++) {
const rsObj = matching[i];
let res = `(PID - ${rsObj.pid}) ${rsObj.filename}`;
for (let j = 0; j < rsObj.args.length; ++j) {
res += " " + rsObj.args[j].toString();
}
terminal.print(res);
}
}
if(args.length === 0){
for (let i = 0; i < server.runningScripts.length; i++) {
const rsObj = server.runningScripts[i];
let res = `(PID - ${rsObj.pid}) ${rsObj.filename}`;
for (let j = 0; j < rsObj.args.length; ++j) {
res += " " + rsObj.args[j].toString();
}
terminal.print(res);
}
terminal.print(res);
}
}

@ -350,7 +350,12 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
event.preventDefault();
modifyInput("deletewordbefore");
}
if (event.keyCode === KEY.D && event.altKey) {
event.preventDefault();
modifyInput("deletewordafter");
}
if (event.keyCode === KEY.U && event.ctrlKey) {
event.preventDefault();
modifyInput("clearbefore");
@ -360,9 +365,6 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
event.preventDefault();
modifyInput("clearafter");
}
// TODO AFTER THIS:
// alt + d deletes word after cursor
}
}

@ -292,7 +292,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
<Tooltip
title={
<Typography>
The maximum number of entries that can be written to a the terminal. Setting this too high can cause
The maximum number of entries that can be written to the terminal. Setting this too high can cause
the game to use a lot of memory.
</Typography>
}

@ -77,6 +77,16 @@ interface IProps {
const useStyles = makeStyles((theme: Theme) =>
createStyles({
title: {
"&.is-minimized + *": {
border: "none",
margin: 0,
"max-height": 0,
padding: 0,
"pointer-events": "none",
visibility: "hidden"
},
},
logs: {
overflowY: "scroll",
overflowX: "hidden",
@ -107,6 +117,7 @@ function LogWindow(props: IProps): React.ReactElement {
const classes = useStyles();
const container = useRef<HTMLDivElement>(null);
const setRerender = useState(false)[1];
const [minimized, setMinimized] = useState(false);
function rerender(): void {
setRerender((old) => !old);
}
@ -140,15 +151,19 @@ function LogWindow(props: IProps): React.ReactElement {
rerender();
}
function title(): string {
function title(full = false): string {
const maxLength = 30;
const t = `${script.filename} ${script.args.map((x: any): string => `${x}`).join(" ")}`;
if (t.length <= maxLength) {
if (full || t.length <= maxLength) {
return t;
}
return t.slice(0, maxLength - 3) + "...";
}
function minimize(): void {
setMinimized(!minimized);
}
function lineClass(s: string): string {
if (s.match(/(^\[[^\]]+\] )?ERROR/) || s.match(/(^\[[^\]]+\] )?FAIL/)) {
return classes.error;
@ -180,18 +195,20 @@ function LogWindow(props: IProps): React.ReactElement {
>
<div onMouseDown={updateLayer}>
<Paper
className={classes.title + " " + (minimized ? 'is-minimized' : '')}
style={{
cursor: "grab",
}}
>
<Box className="drag" display="flex" alignItems="center">
<Typography color="primary" variant="h6">
<Typography color="primary" variant="h6" title={title(true)}>
{title()}
</Typography>
<Box position="absolute" right={0}>
{!workerScripts.has(script.pid) && <Button onClick={run}>Run</Button>}
{workerScripts.has(script.pid) && <Button onClick={kill}>Kill</Button>}
<Button onClick={minimize}>{minimized ? "\u{1F5D6}" : "\u{1F5D5}"}</Button>
<Button onClick={props.onClose}>Close</Button>
</Box>
</Box>

@ -9,6 +9,7 @@ interface IProgressProps {
min: number;
max: number;
current: number;
remaining: number;
progress: number;
color?: React.CSSProperties["color"];
}
@ -18,14 +19,14 @@ interface IStatsOverviewCellProps {
color?: React.CSSProperties["color"];
}
export function StatsProgressBar({ min, max, current, progress, color }: IProgressProps): React.ReactElement {
export function StatsProgressBar({ min, max, current, remaining, progress, color }: IProgressProps): React.ReactElement {
const tooltip = (
<Typography sx={{ textAlign: 'right' }}>
<strong>Progress:</strong>&nbsp;
{numeralWrapper.formatExp(current - min)} / {numeralWrapper.formatExp(max - min)}
{numeralWrapper.formatExp(current)} / {numeralWrapper.formatExp(max - min)}
<br />
<strong>Remaining:</strong>&nbsp;
{numeralWrapper.formatExp(max - current)} ({progress.toFixed(2)}%)
{numeralWrapper.formatExp(remaining)} ({progress.toFixed(2)}%)
</Typography>
);
@ -58,7 +59,8 @@ export function StatsProgressOverviewCell({ progress: skill, color }: IStatsOver
<StatsProgressBar
min={skill.baseExperience}
max={skill.nextExperience}
current={skill.experience}
current={skill.currentExperience}
remaining={skill.remainingExperience}
progress={skill.progress}
color={color}
/>

@ -62,20 +62,14 @@ export function WorkInProgressRoot(): React.ReactElement {
<Reputation reputation={player.workRepGained} /> (
<ReputationRate reputation={player.workRepGainRate * CYCLES_PER_SEC} />) reputation for this faction <br />
<br />
{numeralWrapper.formatExp(player.workHackExpGained)} (
{numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp <br />
{player.workHackExpGained > 0 && <>{numeralWrapper.formatExp(player.workHackExpGained)} ({numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp <br /></>}
<br />
{numeralWrapper.formatExp(player.workStrExpGained)} (
{numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp <br />
{numeralWrapper.formatExp(player.workDefExpGained)} (
{numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp <br />
{numeralWrapper.formatExp(player.workDexExpGained)} (
{numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp <br />
{numeralWrapper.formatExp(player.workAgiExpGained)} (
{numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp <br />
{player.workStrExpGained > 0 && <>{numeralWrapper.formatExp(player.workStrExpGained)} ({numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp <br /></>}
{player.workDefExpGained > 0 && <>{numeralWrapper.formatExp(player.workDefExpGained)} ({numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp <br /></>}
{player.workDexExpGained > 0 && <>{numeralWrapper.formatExp(player.workDexExpGained)} ({numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp <br /></>}
{player.workAgiExpGained > 0 && <>{numeralWrapper.formatExp(player.workAgiExpGained)} ({numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp <br /></>}
<br />
{numeralWrapper.formatExp(player.workChaExpGained)} (
{numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp <br />
{player.workChaExpGained > 0 && <>{numeralWrapper.formatExp(player.workChaExpGained)} ({numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp <br /></>}
<br />
You will automatically finish after working for 20 hours. You can cancel earlier if you wish.
<br />
@ -123,18 +117,12 @@ export function WorkInProgressRoot(): React.ReactElement {
<br />
<br />
You have gained: <br />
{numeralWrapper.formatExp(player.workHackExpGained)} (
{numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp <br />
{numeralWrapper.formatExp(player.workStrExpGained)} (
{numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp <br />
{numeralWrapper.formatExp(player.workDefExpGained)} (
{numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp <br />
{numeralWrapper.formatExp(player.workDexExpGained)} (
{numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp <br />
{numeralWrapper.formatExp(player.workAgiExpGained)} (
{numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp <br />
{numeralWrapper.formatExp(player.workChaExpGained)} (
{numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp <br />
{player.workHackExpGained > 0 && <>{numeralWrapper.formatExp(player.workHackExpGained)} ({numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp <br /></>}
{player.workStrExpGained > 0 && <>{numeralWrapper.formatExp(player.workStrExpGained)} ({numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp <br /></>}
{player.workDefExpGained > 0 && <>{numeralWrapper.formatExp(player.workDefExpGained)} ({numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp <br /></>}
{player.workDexExpGained > 0 && <>{numeralWrapper.formatExp(player.workDexExpGained)} ({numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp <br /></>}
{player.workAgiExpGained > 0 && <>{numeralWrapper.formatExp(player.workAgiExpGained)} ({numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp <br /></>}
{player.workChaExpGained > 0 && <>{numeralWrapper.formatExp(player.workChaExpGained)} ({numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp <br /></>}
You may cancel at any time
</Typography>
</Grid>
@ -185,26 +173,26 @@ export function WorkInProgressRoot(): React.ReactElement {
<Reputation reputation={player.workRepGained} /> (
<ReputationRate reputation={player.workRepGainRate * CYCLES_PER_SEC} />) reputation for this company <br />
<br />
{numeralWrapper.formatExp(player.workHackExpGained)} (
{player.workHackExpGained > 0 && <>{numeralWrapper.formatExp(player.workHackExpGained)} (
{`${numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec`}
) hacking exp <br />
) hacking exp <br /></>}
<br />
{numeralWrapper.formatExp(player.workStrExpGained)} (
{player.workStrExpGained > 0 && <>{numeralWrapper.formatExp(player.workStrExpGained)} (
{`${numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec`}
) strength exp <br />
{numeralWrapper.formatExp(player.workDefExpGained)} (
) strength exp <br /></>}
{player.workDefExpGained > 0 && <>{numeralWrapper.formatExp(player.workDefExpGained)} (
{`${numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec`}
) defense exp <br />
{numeralWrapper.formatExp(player.workDexExpGained)} (
) defense exp <br /></>}
{player.workDexExpGained > 0 && <>{numeralWrapper.formatExp(player.workDexExpGained)} (
{`${numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec`}
) dexterity exp <br />
{numeralWrapper.formatExp(player.workAgiExpGained)} (
) dexterity exp <br /></>}
{player.workAgiExpGained > 0 && <>{numeralWrapper.formatExp(player.workAgiExpGained)} (
{`${numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}
) agility exp <br />
) agility exp <br /></>}
<br />
{numeralWrapper.formatExp(player.workChaExpGained)} (
{player.workChaExpGained > 0 && <>{numeralWrapper.formatExp(player.workChaExpGained)} (
{`${numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}
) charisma exp <br />
) charisma exp <br /></>}
<br />
You will automatically finish after working for 8 hours. You can cancel earlier if you wish, but you will
only gain {penaltyString} of the reputation you've earned so far.
@ -256,26 +244,26 @@ export function WorkInProgressRoot(): React.ReactElement {
<ReputationRate reputation={player.workRepGainRate * CYCLES_PER_SEC} />
) reputation for this company <br />
<br />
{numeralWrapper.formatExp(player.workHackExpGained)} (
{player.workHackExpGained > 0 && <>{numeralWrapper.formatExp(player.workHackExpGained)} (
{`${numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec`}
) hacking exp <br />
) hacking exp <br /></>}
<br />
{numeralWrapper.formatExp(player.workStrExpGained)} (
{player.workStrExpGained > 0 && <>{numeralWrapper.formatExp(player.workStrExpGained)} (
{`${numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec`}
) strength exp <br />
{numeralWrapper.formatExp(player.workDefExpGained)} (
) strength exp <br /></>}
{player.workDefExpGained > 0 && <>{numeralWrapper.formatExp(player.workDefExpGained)} (
{`${numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec`}
) defense exp <br />
{numeralWrapper.formatExp(player.workDexExpGained)} (
) defense exp <br /></>}
{player.workDexExpGained > 0 && <>{numeralWrapper.formatExp(player.workDexExpGained)} (
{`${numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec`}
) dexterity exp <br />
{numeralWrapper.formatExp(player.workAgiExpGained)} (
) dexterity exp <br /></>}
{player.workAgiExpGained > 0 && <>{numeralWrapper.formatExp(player.workAgiExpGained)} (
{`${numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}
) agility exp <br />
) agility exp <br /></>}
<br />
{numeralWrapper.formatExp(player.workChaExpGained)} (
{player.workChaExpGained > 0 && <>{numeralWrapper.formatExp(player.workChaExpGained)} (
{`${numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}
) charisma exp <br />
) charisma exp <br /></>}
<br />
You will automatically finish after working for 8 hours. You can cancel earlier if you wish, and there will
be no penalty because this is a part-time job.