mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-26 01:23:49 +01:00
Merge branch 'dev' into feature/alert-close-browser
This commit is contained in:
commit
a2d75ae10c
@ -121,7 +121,29 @@ Inside the root of the repo run
|
||||
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
|
||||
|
||||
### How to build the electron app
|
||||
|
||||
Tested on Node v16.13.1 (LTS) on Windows
|
||||
These steps only work in a bash-like environment, like MinGW for Windows.
|
||||
|
||||
```sh
|
||||
# Install the main game dependencies & build the app in debug mode
|
||||
npm install
|
||||
npm run build:dev
|
||||
|
||||
# Use electron-packager to build the app to the .build/ folder
|
||||
npm run electron
|
||||
|
||||
# When launching the .exe directly, you'll need the steam_appid.txt file in the root
|
||||
# If not using windows, change this line accordingly
|
||||
cp .build/bitburner-win32-x64/resources/app/steam_appid.txt .build/bitburner-win32-x64/steam_appid.txt
|
||||
|
||||
# And run the game...
|
||||
.build/bitburner-win32-x64/bitburner.exe
|
||||
```
|
||||
|
||||
### Submitting a Pull Request
|
||||
|
||||
When submitting a pull request with your code contributions, please abide by
|
||||
the following rules:
|
||||
|
2
dist/bitburner.d.ts
vendored
2
dist/bitburner.d.ts
vendored
@ -3498,7 +3498,7 @@ export declare interface NS extends Singularity {
|
||||
* Returns 0 if the script does not exist.
|
||||
*
|
||||
* @param script - Filename of script. This is case-sensitive.
|
||||
* @param host - Host of target server the script is located on. This is optional, If it is not specified then the function will se the current server as the target server.
|
||||
* @param host - Host of target server the script is located on. This is optional, If it is not specified then the function will use the current server as the target server.
|
||||
* @returns Amount of RAM required to run the specified script on the target server, and 0 if the script does not exist.
|
||||
*/
|
||||
getScriptRam(script: string, host?: string): number;
|
||||
|
30
electron/export.html
Normal file
30
electron/export.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Bitburner</title>
|
||||
<link rel="stylesheet" href="main.css" />
|
||||
<style>
|
||||
body {
|
||||
background-color: black;
|
||||
color: #0c0;
|
||||
}
|
||||
|
||||
div {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>Close me when operation is completed.</h1>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,11 +1,12 @@
|
||||
/* eslint-disable no-process-exit */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const { app, dialog } = require("electron");
|
||||
const { app, dialog, BrowserWindow } = require("electron");
|
||||
const log = require("electron-log");
|
||||
const greenworks = require("./greenworks");
|
||||
const api = require("./api-server");
|
||||
const gameWindow = require("./gameWindow");
|
||||
const achievements = require("./achievements");
|
||||
const utils = require("./utils");
|
||||
|
||||
log.catchErrors();
|
||||
log.info(`Started app: ${JSON.stringify(process.argv)}`);
|
||||
@ -100,7 +101,16 @@ global.app_handlers = {
|
||||
createWindow: startWindow,
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
app.whenReady().then(async () => {
|
||||
log.info('Application is ready!');
|
||||
startWindow(process.argv.includes("--no-scripts"));
|
||||
|
||||
if (process.argv.includes("--export-save")) {
|
||||
const window = new BrowserWindow({ show: false });
|
||||
await window.loadFile("export.html", false);
|
||||
window.show();
|
||||
setStopProcessHandler(app, window, true);
|
||||
await utils.exportSave(window);
|
||||
} else {
|
||||
startWindow(process.argv.includes("--no-scripts"));
|
||||
}
|
||||
});
|
||||
|
@ -62,7 +62,38 @@ function showErrorBox(title, error) {
|
||||
);
|
||||
}
|
||||
|
||||
function exportSaveFromIndexedDb() {
|
||||
return new Promise((resolve) => {
|
||||
const dbRequest = indexedDB.open("bitburnerSave");
|
||||
dbRequest.onsuccess = () => {
|
||||
const db = dbRequest.result;
|
||||
const transaction = db.transaction(['savestring'], "readonly");
|
||||
const store = transaction.objectStore('savestring');
|
||||
const request = store.get('save');
|
||||
request.onsuccess = () => {
|
||||
const file = new Blob([request.result], {type: 'text/plain'});
|
||||
const a = document.createElement("a");
|
||||
const url = URL.createObjectURL(file);
|
||||
a.href = url;
|
||||
a.download = 'save.json';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
resolve();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function exportSave(window) {
|
||||
await window.webContents
|
||||
.executeJavaScript(`${exportSaveFromIndexedDb.toString()}; exportSaveFromIndexedDb();`, true);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
reloadAndKill, showErrorBox,
|
||||
reloadAndKill, showErrorBox, exportSave,
|
||||
attachUnresponsiveAppHandler, detachUnresponsiveAppHandler,
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
## NS.hackAnalyze() method
|
||||
|
||||
Get the percent of money stolen with a single thread.
|
||||
Get the part of money stolen with a single thread.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
@ -22,13 +22,13 @@ hackAnalyze(host: string): number;
|
||||
|
||||
number
|
||||
|
||||
The percentage of money you will steal from the target server with a single hack.
|
||||
The part of money you will steal from the target server with a single thread hack.
|
||||
|
||||
## Remarks
|
||||
|
||||
RAM cost: 1 GB
|
||||
|
||||
Returns the percentage of the specified server’s money you will steal with a single hack. This value is returned in percentage form, not decimal (Netscript functions typically return in decimal form, but not this one).
|
||||
Returns the part of the specified server’s money you will steal with a single thread hack.
|
||||
|
||||
## Example
|
||||
|
||||
@ -36,6 +36,6 @@ Returns the percentage of the specified server’s money you will steal with a s
|
||||
```ts
|
||||
//For example, assume the following returns 0.01:
|
||||
hackAnalyze("foodnstuff");
|
||||
//This means that if hack the foodnstuff server, then you will steal 1% of its total money. If you hack using N threads, then you will steal N*0.01 times its total money.
|
||||
//This means that if hack the foodnstuff server using a single thread, then you will steal 1%, or 0.01 of its total money. If you hack using N threads, then you will steal N*0.01 times its total money.
|
||||
```
|
||||
|
||||
|
@ -111,7 +111,7 @@ export async function main(ns) {
|
||||
| [growthAnalyze(host, growthAmount, cores)](./bitburner.ns.growthanalyze.md) | Calculate the number of grow thread needed to grow a server by a certain multiplier. |
|
||||
| [growthAnalyzeSecurity(threads)](./bitburner.ns.growthanalyzesecurity.md) | Calculate the security increase for a number of thread. |
|
||||
| [hack(host, opts)](./bitburner.ns.hack.md) | Steal a servers money. |
|
||||
| [hackAnalyze(host)](./bitburner.ns.hackanalyze.md) | Get the percent of money stolen with a single thread. |
|
||||
| [hackAnalyze(host)](./bitburner.ns.hackanalyze.md) | Get the part of money stolen with a single thread. |
|
||||
| [hackAnalyzeChance(host)](./bitburner.ns.hackanalyzechance.md) | Get the chance of successfully hacking a server. |
|
||||
| [hackAnalyzeSecurity(threads)](./bitburner.ns.hackanalyzesecurity.md) | Get the security increase for a number of thread. |
|
||||
| [hackAnalyzeThreads(host, hackAmount)](./bitburner.ns.hackanalyzethreads.md) | Predict the effect of hack. |
|
||||
|
@ -30,7 +30,7 @@ RAM cost: 5 GB
|
||||
|
||||
This function is used to automatically attempt to commit crimes. If you are already in the middle of some ‘working’ action (such as working for a company or training at a gym), then running this function will automatically cancel that action and give you your earnings.
|
||||
|
||||
This function returns the number of seconds it takes to attempt the specified crime (e.g It takes 60 seconds to attempt the ‘Rob Store’ crime, so running `commitCrime('rob store')` will return 60).
|
||||
This function returns the number of milliseconds it takes to attempt the specified crime (e.g It takes 60 seconds to attempt the ‘Rob Store’ crime, so running `commitCrime('rob store')` will return 60,000).
|
||||
|
||||
Warning: I do not recommend using the time returned from this function to try and schedule your crime attempts. Instead, I would use the isBusy Singularity function to check whether you have finished attempting a crime. This is because although the game sets a certain crime to be X amount of seconds, there is no guarantee that your browser will follow that time limit.
|
||||
|
||||
|
@ -16,4 +16,10 @@ cp main.css .package/main.css
|
||||
cp dist/vendor.bundle.js .package/dist/vendor.bundle.js
|
||||
cp main.bundle.js .package/main.bundle.js
|
||||
|
||||
# Install electron sub-dependencies
|
||||
cd electron
|
||||
npm install
|
||||
cd ..
|
||||
|
||||
# And finally build the app.
|
||||
npm run electron:packager
|
||||
|
@ -38,13 +38,14 @@ export function printAliases(): void {
|
||||
export function parseAliasDeclaration(dec: string, global = false): boolean {
|
||||
const re = /^([\w|!|%|,|@|-]+)=(("(.+)")|('(.+)'))$/;
|
||||
const matches = dec.match(re);
|
||||
if (matches == null || matches.length != 3) {
|
||||
if (matches == null || matches.length != 7) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (global) {
|
||||
addGlobalAlias(matches[1], matches[2]);
|
||||
addGlobalAlias(matches[1], matches[4] || matches[6]);
|
||||
} else {
|
||||
addAlias(matches[1], matches[2]);
|
||||
addAlias(matches[1], matches[4] || matches[6]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1925,7 +1925,7 @@ function initAugmentations(): void {
|
||||
repCost: 7.5e3,
|
||||
moneyCost: 3e7,
|
||||
info:
|
||||
"A tiny chip that sits behind the retinae. This implant lets the" + "user visually detect infrared radiation.",
|
||||
"A tiny chip that sits behind the retinae. This implant lets the user visually detect infrared radiation.",
|
||||
crime_success_mult: 1.25,
|
||||
crime_money_mult: 1.1,
|
||||
dexterity_mult: 1.1,
|
||||
|
@ -108,31 +108,22 @@ function WarehouseRoot(props: IProps): React.ReactElement {
|
||||
}
|
||||
}
|
||||
|
||||
let breakdown = <></>;
|
||||
const breakdownItems: string[] = [];
|
||||
for (const matName in props.warehouse.materials) {
|
||||
const mat = props.warehouse.materials[matName];
|
||||
if (!MaterialSizes.hasOwnProperty(matName)) continue;
|
||||
if (mat.qty === 0) continue;
|
||||
breakdown = (
|
||||
<>
|
||||
{breakdown}
|
||||
{matName}: {numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0.0")}
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
breakdownItems.push(`${matName}: ${numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0.0")}`);
|
||||
}
|
||||
|
||||
for (const prodName in division.products) {
|
||||
const prod = division.products[prodName];
|
||||
if (prod === undefined) continue;
|
||||
breakdown = (
|
||||
<>
|
||||
{breakdown}
|
||||
{prodName}: {numeralWrapper.format(prod.data[props.warehouse.loc][0] * prod.siz, "0,0.0")}
|
||||
</>
|
||||
);
|
||||
breakdownItems.push(`${prodName}: ${numeralWrapper.format(prod.data[props.warehouse.loc][0] * prod.siz, "0,0.0")}`);
|
||||
}
|
||||
|
||||
const breakdown = <>{breakdownItems.join('<br />')}</>
|
||||
|
||||
return (
|
||||
<Paper>
|
||||
<Box display="flex" alignItems="center">
|
||||
|
@ -29,6 +29,7 @@ import { GetServer } from "../../Server/AllServers";
|
||||
import { CorruptableText } from "../../ui/React/CorruptableText";
|
||||
import { use } from "../../ui/Context";
|
||||
import { serverMetadata } from "../../Server/data/servers";
|
||||
import { Tooltip } from "@mui/material";
|
||||
|
||||
type IProps = {
|
||||
loc: Location;
|
||||
@ -92,8 +93,11 @@ export function GenericLocation({ loc }: IProps): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => router.toCity()}>Return to World</Button>
|
||||
<Typography variant="h4">
|
||||
{backdoorInstalled && !Settings.DisableTextEffects ? <CorruptableText content={loc.name} /> : loc.name}
|
||||
<Typography variant="h4" sx={{ mt: 1 }}>
|
||||
{backdoorInstalled && !Settings.DisableTextEffects ? (
|
||||
<Tooltip title={`Backdoor installed on ${loc.name}.`}>
|
||||
<span><CorruptableText content={loc.name} /></span>
|
||||
</Tooltip>) : loc.name}
|
||||
</Typography>
|
||||
{locContent}
|
||||
</>
|
||||
|
@ -1009,7 +1009,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
workerScript.log("spawn", () => "Exiting...");
|
||||
}
|
||||
},
|
||||
kill: function (filename: any, hostname: any, ...scriptArgs: any): any {
|
||||
kill: function (filename: any, hostname?: any, ...scriptArgs: any): any {
|
||||
updateDynamicRam("kill", getRamCost("kill"));
|
||||
|
||||
let res;
|
||||
@ -2025,7 +2025,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
|
||||
return calculateWeakenTime(server, Player) * 1000;
|
||||
},
|
||||
getScriptIncome: function (scriptname: any, hostname: any, ...args: any[]): any {
|
||||
getScriptIncome: function (scriptname?: any, hostname?: any, ...args: any[]): any {
|
||||
updateDynamicRam("getScriptIncome", getRamCost("getScriptIncome"));
|
||||
if (arguments.length === 0) {
|
||||
const res = [];
|
||||
@ -2054,7 +2054,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
return runningScriptObj.onlineMoneyMade / runningScriptObj.onlineRunningTime;
|
||||
}
|
||||
},
|
||||
getScriptExpGain: function (scriptname: any, hostname: any, ...args: any[]): any {
|
||||
getScriptExpGain: function (scriptname?: any, hostname?: any, ...args: any[]): any {
|
||||
updateDynamicRam("getScriptExpGain", getRamCost("getScriptExpGain"));
|
||||
if (arguments.length === 0) {
|
||||
let total = 0;
|
||||
|
@ -1690,6 +1690,11 @@ export function applyForJob(this: IPlayer, entryPosType: CompanyPosition, sing =
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if this company has the position
|
||||
if (!company.hasPosition(pos)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const newPos = getNextCompanyPositionHelper(pos);
|
||||
if (newPos == null) {
|
||||
@ -1863,9 +1868,14 @@ export function applyForAgentJob(this: IPlayer, sing = false): boolean {
|
||||
|
||||
export function applyForEmployeeJob(this: IPlayer, sing = false): boolean {
|
||||
const company = Companies[this.location]; //Company being applied to
|
||||
if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[1]])) {
|
||||
const position = posNames.MiscCompanyPositions[1];
|
||||
// Check if this company has the position
|
||||
if (!company.hasPosition(position)) {
|
||||
return false;
|
||||
}
|
||||
if (this.isQualified(company, CompanyPositions[position])) {
|
||||
this.companyName = company.name;
|
||||
this.jobs[company.name] = posNames.MiscCompanyPositions[1];
|
||||
this.jobs[company.name] = position;
|
||||
if (!sing) {
|
||||
dialogBoxCreate("Congratulations, you are now employed at " + this.location);
|
||||
}
|
||||
@ -1882,8 +1892,13 @@ export function applyForEmployeeJob(this: IPlayer, sing = false): boolean {
|
||||
|
||||
export function applyForPartTimeEmployeeJob(this: IPlayer, sing = false): boolean {
|
||||
const company = Companies[this.location]; //Company being applied to
|
||||
if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[1]])) {
|
||||
this.jobs[company.name] = posNames.PartTimeCompanyPositions[1];
|
||||
const position = posNames.PartTimeCompanyPositions[1];
|
||||
// Check if this company has the position
|
||||
if (!company.hasPosition(position)) {
|
||||
return false;
|
||||
}
|
||||
if (this.isQualified(company, CompanyPositions[position])) {
|
||||
this.jobs[company.name] = position;
|
||||
if (!sing) {
|
||||
dialogBoxCreate("Congratulations, you are now employed part-time at " + this.location);
|
||||
}
|
||||
@ -1900,9 +1915,14 @@ export function applyForPartTimeEmployeeJob(this: IPlayer, sing = false): boolea
|
||||
|
||||
export function applyForWaiterJob(this: IPlayer, sing = false): boolean {
|
||||
const company = Companies[this.location]; //Company being applied to
|
||||
if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[0]])) {
|
||||
const position = posNames.MiscCompanyPositions[0];
|
||||
// Check if this company has the position
|
||||
if (!company.hasPosition(position)) {
|
||||
return false;
|
||||
}
|
||||
if (this.isQualified(company, CompanyPositions[position])) {
|
||||
this.companyName = company.name;
|
||||
this.jobs[company.name] = posNames.MiscCompanyPositions[0];
|
||||
this.jobs[company.name] = position;
|
||||
if (!sing) {
|
||||
dialogBoxCreate("Congratulations, you are now employed as a waiter at " + this.location);
|
||||
}
|
||||
@ -1917,9 +1937,14 @@ export function applyForWaiterJob(this: IPlayer, sing = false): boolean {
|
||||
|
||||
export function applyForPartTimeWaiterJob(this: IPlayer, sing = false): boolean {
|
||||
const company = Companies[this.location]; //Company being applied to
|
||||
if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[0]])) {
|
||||
const position = posNames.PartTimeCompanyPositions[0];
|
||||
// Check if this company has the position
|
||||
if (!company.hasPosition(position)) {
|
||||
return false;
|
||||
}
|
||||
if (this.isQualified(company, CompanyPositions[position])) {
|
||||
this.companyName = company.name;
|
||||
this.jobs[company.name] = posNames.PartTimeCompanyPositions[0];
|
||||
this.jobs[company.name] = position;
|
||||
if (!sing) {
|
||||
dialogBoxCreate("Congratulations, you are now employed as a part-time waiter at " + this.location);
|
||||
}
|
||||
|
11
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
11
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -1767,7 +1767,7 @@ export interface Singularity {
|
||||
*
|
||||
* This function returns the number of milliseconds it takes to attempt the
|
||||
* specified crime (e.g It takes 60 seconds to attempt the ‘Rob Store’ crime,
|
||||
* so running `commitCrime('rob store')` will return 60000).
|
||||
* so running `commitCrime('rob store')` will return 60,000).
|
||||
*
|
||||
* Warning: I do not recommend using the time returned from this function to try
|
||||
* and schedule your crime attempts. Instead, I would use the isBusy Singularity
|
||||
@ -4680,8 +4680,9 @@ export interface NS extends Singularity {
|
||||
* @param args - Arguments to identify which script to kill.
|
||||
* @returns True if the script is successfully killed, and false otherwise.
|
||||
*/
|
||||
kill(script: string | number, host: string, ...args: string[]): boolean;
|
||||
|
||||
kill(script: number): boolean;
|
||||
kill(script: string, host: string, ...args: string[]): boolean;
|
||||
|
||||
/**
|
||||
* Terminate all scripts on a server.
|
||||
* @remarks
|
||||
@ -5543,7 +5544,8 @@ export interface NS extends Singularity {
|
||||
* @param args - Arguments that the script is running with.
|
||||
* @returns Amount of income the specified script generates while online.
|
||||
*/
|
||||
getScriptIncome(script: string, host: string, ...args: string[]): number | [number, number];
|
||||
getScriptIncome(): [number, number];
|
||||
getScriptIncome(script: string, host: string, ...args: string[]): number;
|
||||
|
||||
/**
|
||||
* Get the exp gain of a script.
|
||||
@ -5562,6 +5564,7 @@ export interface NS extends Singularity {
|
||||
* @param args - Arguments that the script is running with.
|
||||
* @returns Amount of hacking experience the specified script generates while online.
|
||||
*/
|
||||
getScriptExpGain(): number;
|
||||
getScriptExpGain(script: string, host: string, ...args: string[]): number;
|
||||
|
||||
/**
|
||||
|
@ -178,7 +178,12 @@ export function Root(props: IProps): React.ReactElement {
|
||||
save();
|
||||
});
|
||||
MonacoVim.VimMode.Vim.defineEx("quit", "q", function () {
|
||||
props.router.toTerminal();
|
||||
});
|
||||
// "wqriteandquit" is not a typo, prefix must be found in full string
|
||||
MonacoVim.VimMode.Vim.defineEx("wqriteandquit", "wq", function () {
|
||||
save();
|
||||
props.router.toTerminal();
|
||||
});
|
||||
editor.focus();
|
||||
});
|
||||
|
@ -113,6 +113,11 @@ interface IDefaultSettings {
|
||||
* Theme colors
|
||||
*/
|
||||
theme: ITheme;
|
||||
|
||||
/*
|
||||
* Use GiB instead of GB
|
||||
*/
|
||||
UseIEC60027_2: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -160,6 +165,7 @@ export const defaultSettings: IDefaultSettings = {
|
||||
SuppressBladeburnerPopup: false,
|
||||
SuppressTIXPopup: false,
|
||||
SuppressSavedGameToast: false,
|
||||
UseIEC60027_2: false,
|
||||
|
||||
theme: defaultTheme,
|
||||
};
|
||||
@ -192,6 +198,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
|
||||
SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup,
|
||||
SuppressTIXPopup: defaultSettings.SuppressTIXPopup,
|
||||
SuppressSavedGameToast: defaultSettings.SuppressSavedGameToast,
|
||||
UseIEC60027_2: defaultSettings.UseIEC60027_2,
|
||||
MonacoTheme: "monokai",
|
||||
MonacoInsertSpaces: false,
|
||||
MonacoFontSize: 20,
|
||||
|
@ -408,4 +408,206 @@ export const getPredefinedThemes = (): IMap<IPredefinedTheme> => ({
|
||||
button: "#000000",
|
||||
},
|
||||
},
|
||||
|
||||
Discord: {
|
||||
credit: "Thermite",
|
||||
description: "Discord inspired theme",
|
||||
reference: "https://discord.com/channels/415207508303544321/921991895230611466/924305252017143818",
|
||||
colors: {
|
||||
primarylight: "#7389DC",
|
||||
primary: "#7389DC",
|
||||
primarydark: "#5964F1",
|
||||
successlight: "#00CC00",
|
||||
success: "#20DF20",
|
||||
successdark: "#0CB80C",
|
||||
errorlight: "#EA5558",
|
||||
error: "#EC4145",
|
||||
errordark: "#E82528",
|
||||
secondarylight: "#C3C3C3",
|
||||
secondary: "#9C9C9C",
|
||||
secondarydark: "#4E4E4E",
|
||||
warninglight: "#ff0",
|
||||
warning: "#cc0",
|
||||
warningdark: "#990",
|
||||
infolight: "#69f",
|
||||
info: "#36c",
|
||||
infodark: "#1C4FB3",
|
||||
welllight: "#999999",
|
||||
well: "#35383C",
|
||||
white: "#FFFFFF",
|
||||
black: "#202225",
|
||||
hp: "#FF5656",
|
||||
money: "#43FF43",
|
||||
hack: "#FFAB3D",
|
||||
combat: "#8A90FD",
|
||||
cha: "#FF51D9",
|
||||
int: "#6495ed",
|
||||
rep: "#FFFF30",
|
||||
disabled: "#474B51",
|
||||
backgroundprimary: "#2F3136",
|
||||
backgroundsecondary: "#35393E",
|
||||
button: "#333",
|
||||
},
|
||||
},
|
||||
|
||||
"One Dark": {
|
||||
credit: "Dexalt142",
|
||||
reference: "https://discord.com/channels/415207508303544321/921991895230611466/924650660694208512",
|
||||
colors: {
|
||||
primarylight: "#98C379",
|
||||
primary: "#98C379",
|
||||
primarydark: "#98C379",
|
||||
successlight: "#98C379",
|
||||
success: "#98C379",
|
||||
successdark: "#98C379",
|
||||
errorlight: "#E06C75",
|
||||
error: "#BE5046",
|
||||
errordark: "#BE5046",
|
||||
secondarylight: "#AAA",
|
||||
secondary: "#888",
|
||||
secondarydark: "#666",
|
||||
warninglight: "#E5C07B",
|
||||
warning: "#E5C07B",
|
||||
warningdark: "#D19A66",
|
||||
infolight: "#61AFEF",
|
||||
info: "#61AFEF",
|
||||
infodark: "#61AFEF",
|
||||
welllight: "#4B5263",
|
||||
well: "#282C34",
|
||||
white: "#ABB2BF",
|
||||
black: "#282C34",
|
||||
hp: "#E06C75",
|
||||
money: "#E5C07B",
|
||||
hack: "#98C379",
|
||||
combat: "#ABB2BF",
|
||||
cha: "#C678DD",
|
||||
int: "#61AFEF",
|
||||
rep: "#ABB2BF",
|
||||
disabled: "#56B6C2",
|
||||
backgroundprimary: "#282C34",
|
||||
backgroundsecondary: "#21252B",
|
||||
button: "#4B5263",
|
||||
},
|
||||
},
|
||||
|
||||
"Muted Gold & Blue": {
|
||||
credit: "Sloth",
|
||||
reference: "https://discord.com/channels/415207508303544321/921991895230611466/924672660758208563",
|
||||
colors: {
|
||||
primarylight: "#E3B54A",
|
||||
primary: "#CAA243",
|
||||
primarydark: "#7E6937",
|
||||
successlight: "#82FF82",
|
||||
success: "#6FDA6F",
|
||||
successdark: "#64C364",
|
||||
errorlight: "#FD5555",
|
||||
error: "#D84A4A",
|
||||
errordark: "#AC3939",
|
||||
secondarylight: "#D8D0B8",
|
||||
secondary: "#B1AA95",
|
||||
secondarydark: "#736E5E",
|
||||
warninglight: "#ff0",
|
||||
warning: "#cc0",
|
||||
warningdark: "#990",
|
||||
infolight: "#69f",
|
||||
info: "#36c",
|
||||
infodark: "#039",
|
||||
welllight: "#444",
|
||||
well: "#111111",
|
||||
white: "#fff",
|
||||
black: "#070300",
|
||||
hp: "#dd3434",
|
||||
money: "#ffd700",
|
||||
hack: "#adff2f",
|
||||
combat: "#faffdf",
|
||||
cha: "#a671d1",
|
||||
int: "#6495ed",
|
||||
rep: "#faffdf",
|
||||
disabled: "#66cfbc",
|
||||
backgroundprimary: "#0A0A0E",
|
||||
backgroundsecondary: "#0E0E10",
|
||||
button: "#222222",
|
||||
},
|
||||
},
|
||||
|
||||
"Default Lite": {
|
||||
credit: "NmuGmu",
|
||||
description: "Less eye-straining default theme",
|
||||
reference: "https://discord.com/channels/415207508303544321/921991895230611466/925263801564151888",
|
||||
colors: {
|
||||
primarylight: "#28CF28",
|
||||
primary: "#21A821",
|
||||
primarydark: "#177317",
|
||||
successlight: "#1CFF1C",
|
||||
success: "#16CA16",
|
||||
successdark: "#0D910D",
|
||||
errorlight: "#FF3B3B",
|
||||
error: "#C32D2D",
|
||||
errordark: "#8E2121",
|
||||
secondarylight: "#B3B3B3",
|
||||
secondary: "#838383",
|
||||
secondarydark: "#676767",
|
||||
warninglight: "#FFFF3A",
|
||||
warning: "#C3C32A",
|
||||
warningdark: "#8C8C1E",
|
||||
infolight: "#64CBFF",
|
||||
info: "#3399CC",
|
||||
infodark: "#246D91",
|
||||
welllight: "#404040",
|
||||
well: "#1C1C1C",
|
||||
white: "#C3C3C3",
|
||||
black: "#0A0B0B",
|
||||
hp: "#C62E2E",
|
||||
money: "#D6BB27",
|
||||
hack: "#ADFF2F",
|
||||
combat: "#E8EDCD",
|
||||
cha: "#8B5FAF",
|
||||
int: "#537CC8",
|
||||
rep: "#E8EDCD",
|
||||
disabled: "#5AB5A5",
|
||||
backgroundprimary: "#0C0D0E",
|
||||
backgroundsecondary: "#121415",
|
||||
button: "#252829",
|
||||
},
|
||||
},
|
||||
|
||||
Light: {
|
||||
credit: "matt",
|
||||
reference: "https://discord.com/channels/415207508303544321/921991895230611466/926114005456658432",
|
||||
colors: {
|
||||
primarylight: "#535353",
|
||||
primary: "#1A1A1A",
|
||||
primarydark: "#0d0d0d",
|
||||
successlight: "#63c439",
|
||||
success: "#428226",
|
||||
successdark: "#2E5A1B",
|
||||
errorlight: "#df7051",
|
||||
error: "#C94824",
|
||||
errordark: "#91341B",
|
||||
secondarylight: "#b3b3b3",
|
||||
secondary: "#9B9B9B",
|
||||
secondarydark: "#7A7979",
|
||||
warninglight: "#e8d464",
|
||||
warning: "#C6AD20",
|
||||
warningdark: "#9F8A16",
|
||||
infolight: "#6299cf",
|
||||
info: "#3778B7",
|
||||
infodark: "#30689C",
|
||||
welllight: "#f9f9f9",
|
||||
well: "#eaeaea",
|
||||
white: "#F7F7F7",
|
||||
black: "#F7F7F7",
|
||||
hp: "#BF5C41",
|
||||
money: "#E1B121",
|
||||
hack: "#47BC38",
|
||||
combat: "#656262",
|
||||
cha: "#A568AC",
|
||||
int: "#889BCF",
|
||||
rep: "#656262",
|
||||
disabled: "#70B4BF",
|
||||
backgroundprimary: "#F7F7F7",
|
||||
backgroundsecondary: "#f9f9f9",
|
||||
button: "#eaeaea",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -32,10 +32,10 @@ StockSymbols[LocationName.VolhavenCompuTek] = "CTK";
|
||||
StockSymbols[LocationName.AevumNetLinkTechnologies] = "NTLK";
|
||||
StockSymbols[LocationName.IshimaOmegaSoftware] = "OMGA";
|
||||
StockSymbols[LocationName.Sector12FoodNStuff] = "FNS";
|
||||
StockSymbols[LocationName.Sector12JoesGuns] = "JGN";
|
||||
|
||||
// Stocks for other companies
|
||||
StockSymbols["Sigma Cosmetics"] = "SGC";
|
||||
StockSymbols["Joes Guns"] = "JGN";
|
||||
StockSymbols["Catalyst Ventures"] = "CTYS";
|
||||
StockSymbols["Microdyne Technologies"] = "MDYN";
|
||||
StockSymbols["Titan Laboratories"] = "TITN";
|
||||
|
@ -176,7 +176,7 @@ export function InfoAndPurchases(props: IProps): React.ReactElement {
|
||||
<>
|
||||
<Typography>Welcome to the World Stock Exchange (WSE)!</Typography>
|
||||
<Link href={documentationLink} target={"_blank"}>
|
||||
Investopedia
|
||||
<Typography>Investopedia</Typography>
|
||||
</Link>
|
||||
<br />
|
||||
<PurchaseWseAccountButton {...props} />
|
||||
|
@ -28,6 +28,7 @@ import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
|
||||
import { dialogBoxCreate } from "./DialogBox";
|
||||
import { ConfirmationModal } from "./ConfirmationModal";
|
||||
import { ThemeEditorModal } from "./ThemeEditorModal";
|
||||
import { SnackbarEvents } from "./Snackbar";
|
||||
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { save, deleteGame } from "../../db";
|
||||
@ -51,6 +52,12 @@ interface IProps {
|
||||
softReset: () => void;
|
||||
}
|
||||
|
||||
interface ImportData {
|
||||
base64: string;
|
||||
parsed: any;
|
||||
exportDate?: Date;
|
||||
}
|
||||
|
||||
export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
const importInput = useRef<HTMLInputElement>(null);
|
||||
@ -78,12 +85,15 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
const [enableBashHotkeys, setEnableBashHotkeys] = useState(Settings.EnableBashHotkeys);
|
||||
const [timestampFormat, setTimestampFormat] = useState(Settings.TimestampsFormat);
|
||||
const [saveGameOnFileSave, setSaveGameOnFileSave] = useState(Settings.SaveGameOnFileSave);
|
||||
const [useIEC60027_2, setUseIEC60027_2] = useState(Settings.UseIEC60027_2);
|
||||
|
||||
const [locale, setLocale] = useState(Settings.Locale);
|
||||
const [diagnosticOpen, setDiagnosticOpen] = useState(false);
|
||||
const [deleteGameOpen, setDeleteOpen] = useState(false);
|
||||
const [themeEditorOpen, setThemeEditorOpen] = useState(false);
|
||||
const [softResetOpen, setSoftResetOpen] = useState(false);
|
||||
const [importSaveOpen, setImportSaveOpen] = useState(false);
|
||||
const [importData, setImportData] = useState<ImportData | null>(null);
|
||||
|
||||
function handleExecTimeChange(event: any, newValue: number | number[]): void {
|
||||
setExecTime(newValue as number);
|
||||
@ -154,6 +164,10 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
setDisableASCIIArt(event.target.checked);
|
||||
Settings.DisableASCIIArt = event.target.checked;
|
||||
}
|
||||
function handleUseIEC60027_2Change(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setUseIEC60027_2(event.target.checked);
|
||||
Settings.UseIEC60027_2 = event.target.checked;
|
||||
}
|
||||
|
||||
function handleDisableTextEffectsChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setDisableTextEffects(event.target.checked);
|
||||
@ -206,11 +220,67 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
return;
|
||||
}
|
||||
const contents = result;
|
||||
save(contents).then(() => setTimeout(() => location.reload(), 1000));
|
||||
|
||||
// https://stackoverflow.com/a/35002237
|
||||
const base64regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;
|
||||
if (!base64regex.test(contents)) {
|
||||
SnackbarEvents.emit("Save game was not a base64 string", "error", 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
let newSave;
|
||||
try {
|
||||
newSave = window.atob(contents);
|
||||
newSave = newSave.trim();
|
||||
} catch (error) {
|
||||
console.log(error); // We'll handle below
|
||||
}
|
||||
|
||||
if (!newSave || newSave === '') {
|
||||
SnackbarEvents.emit("Save game had not content", "error", 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
let parsedSave;
|
||||
try {
|
||||
parsedSave = JSON.parse(newSave);
|
||||
} catch (error) {
|
||||
console.log(error); // We'll handle below
|
||||
}
|
||||
|
||||
if (!parsedSave || parsedSave.ctor !== 'BitburnerSaveObject' || !parsedSave.data) {
|
||||
SnackbarEvents.emit("Save game did not seem valid", "error", 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const data: ImportData = {
|
||||
base64: contents,
|
||||
parsed: parsedSave,
|
||||
}
|
||||
|
||||
// We don't always seem to have this value in the save file. Exporting from the option menu does not set the bonus I think.
|
||||
const exportTimestamp = parsedSave.data.LastExportBonus;
|
||||
if (exportTimestamp && exportTimestamp !== '0') {
|
||||
data.exportDate = new Date(parseInt(exportTimestamp, 10))
|
||||
}
|
||||
|
||||
setImportData(data)
|
||||
setImportSaveOpen(true);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
function confirmedImportGame(): void {
|
||||
if (!importData) return;
|
||||
|
||||
setImportSaveOpen(false);
|
||||
save(importData.base64).then(() => {
|
||||
setImportData(null);
|
||||
setTimeout(() => location.reload(), 1000)
|
||||
});
|
||||
}
|
||||
|
||||
function doSoftReset(): void {
|
||||
if (!Settings.SuppressBuyAugmentationConfirmation) {
|
||||
setSoftResetOpen(true);
|
||||
@ -513,6 +583,16 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={useIEC60027_2} onChange={handleUseIEC60027_2Change} />}
|
||||
label={
|
||||
<Tooltip title={<Typography>If this is set all references to memory will use GiB instead of GB, in accordance with IEC 60027-2.</Typography>}>
|
||||
<Typography>Use GiB instead of GB</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Tooltip
|
||||
title={
|
||||
@ -618,19 +698,41 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
<Button onClick={() => setDeleteOpen(true)}>Delete Game</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Tooltip title={<Typography>export</Typography>}>
|
||||
<Tooltip title={<Typography>Export your game to a text file.</Typography>}>
|
||||
<Button onClick={() => props.export()}>
|
||||
<DownloadIcon color="primary" />
|
||||
Export
|
||||
Export Game
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={<Typography>import</Typography>}>
|
||||
<Tooltip title={<Typography>Import your game from a text file.<br/>This will <strong>overwrite</strong> your current game. Back it up first!</Typography>}>
|
||||
<Button onClick={startImport}>
|
||||
<UploadIcon color="primary" />
|
||||
Import
|
||||
Import Game
|
||||
<input ref={importInput} id="import-game-file-selector" type="file" hidden onChange={onImport} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<ConfirmationModal
|
||||
open={importSaveOpen}
|
||||
onClose={() => setImportSaveOpen(false)}
|
||||
onConfirm={() => confirmedImportGame()}
|
||||
confirmationText={
|
||||
<>
|
||||
Importing a new game will <strong>completely wipe</strong> the current data!
|
||||
<br />
|
||||
<br />
|
||||
Make sure to have a backup of your current save file before importing.
|
||||
<br />
|
||||
The file you are attempting to import seems valid.
|
||||
<br />
|
||||
<br />
|
||||
{importData?.exportDate && (<>
|
||||
The export date of the save file is <strong>{importData?.exportDate.toString()}</strong>
|
||||
<br />
|
||||
<br />
|
||||
</>)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Tooltip
|
||||
|
@ -46,6 +46,7 @@ function ColorEditor({ name, onColorChange, color, defaultColor }: IColorEditorP
|
||||
deferred
|
||||
value={color}
|
||||
onChange={(newColor: Color) => onColorChange(name, "#" + newColor.hex)}
|
||||
disableAlpha
|
||||
/>
|
||||
</>
|
||||
),
|
||||
|
@ -14,10 +14,13 @@ import "numeral/locales/no";
|
||||
import "numeral/locales/pl";
|
||||
import "numeral/locales/ru";
|
||||
|
||||
import { Settings } from "../Settings/Settings";
|
||||
|
||||
/* eslint-disable class-methods-use-this */
|
||||
|
||||
const extraFormats = [1e15, 1e18, 1e21, 1e24, 1e27, 1e30];
|
||||
const extraNotations = ["q", "Q", "s", "S", "o", "n"];
|
||||
const gigaMultiplier = { standard: 1e9, iec60027_2: 2 ** 30 };
|
||||
|
||||
class NumeralFormatter {
|
||||
// Default Locale
|
||||
@ -110,11 +113,11 @@ class NumeralFormatter {
|
||||
}
|
||||
|
||||
formatRAM(n: number): string {
|
||||
if (n < 1e3) return this.format(n, "0.00") + "GB";
|
||||
if (n < 1e6) return this.format(n / 1e3, "0.00") + "TB";
|
||||
if (n < 1e9) return this.format(n / 1e6, "0.00") + "PB";
|
||||
if (n < 1e12) return this.format(n / 1e9, "0.00") + "EB";
|
||||
return this.format(n, "0.00") + "GB";
|
||||
if(Settings.UseIEC60027_2)
|
||||
{
|
||||
return this.format(n * gigaMultiplier.iec60027_2, "0.00ib");
|
||||
}
|
||||
return this.format(n * gigaMultiplier.standard, "0.00b");
|
||||
}
|
||||
|
||||
formatPercentage(n: number, decimalPlaces = 2): string {
|
||||
|
Loading…
Reference in New Issue
Block a user