Merge pull request #1348 from danielyxie/dev

Build a bunch of fixes
This commit is contained in:
hydroflame 2021-09-21 20:30:32 -04:00 committed by GitHub
commit 4e82293afb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 1918 additions and 2472 deletions

54
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -28,6 +28,8 @@ grow() Netscript Function
server, but there is no required hacking level to run the command. It also server, but there is no required hacking level to run the command. It also
raises the security level of the target server by 0.004 per thread. raises the security level of the target server by 0.004 per thread.
Action time is calculated at the start, effect is calculated at the end.
Example: Example:
.. code-block:: javascript .. code-block:: javascript

@ -27,6 +27,8 @@ hack() Netscript Function
A successful :doc:`hack<hack>` on a server will raise that server's security A successful :doc:`hack<hack>` on a server will raise that server's security
level by 0.002. level by 0.002.
Action time is calculated at the start, effect is calculated at the end.
Example: Example:
.. code-block:: javascript .. code-block:: javascript

@ -3,7 +3,9 @@ getAugmentationCost() Netscript Function
.. js:function:: getAugmentationCost(augName) .. js:function:: getAugmentationCost(augName)
.. warning:: This function is deprecated. .. warning:: This function is deprecated. It still functions, but new
scripts should prefer :doc:`getAugmentationPrice<getAugmentationPrice>`
and :doc:`getAugmentationRepReq<getAugmentationRepReq>` instead.
:RAM cost: 5 GB :RAM cost: 5 GB

@ -1,4 +1,5 @@
const { app, BrowserWindow, Menu } = require("electron"); const { app, BrowserWindow, Menu, globalShortcut, shell } = require("electron");
Menu.setApplicationMenu(false); Menu.setApplicationMenu(false);
function createWindow() { function createWindow() {
const win = new BrowserWindow({ const win = new BrowserWindow({
@ -12,6 +13,24 @@ function createWindow() {
win.maximize(); win.maximize();
win.loadFile("index.html"); win.loadFile("index.html");
win.show(); win.show();
// win.webContents.openDevTools();
globalShortcut.register("f5", function () {
win.loadFile("index.html");
});
globalShortcut.register("f8", function () {
win.loadFile("index.html", { query: { noScripts: "true" } });
});
win.webContents.on("new-window", function (e, url) {
// make sure local urls stay in electron perimeter
if ("file://" === url.substr(0, "file://".length)) {
return;
}
// and open every other protocols on the browser
e.preventDefault();
shell.openExternal(url);
});
} }
app.whenReady().then(() => { app.whenReady().then(() => {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1357
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -58,7 +58,8 @@
"treant-js": "^1.0.1", "treant-js": "^1.0.1",
"unused-webpack-plugin": "^2.4.0", "unused-webpack-plugin": "^2.4.0",
"uuid": "^3.2.1", "uuid": "^3.2.1",
"w3c-blob": "0.0.1" "w3c-blob": "0.0.1",
"webpack-deadcode-plugin": "^0.1.15"
}, },
"description": "A cyberpunk-themed incremental game", "description": "A cyberpunk-themed incremental game",
"devDependencies": { "devDependencies": {

@ -1,14 +1,19 @@
# npm install electron --save-dev # npm install electron --save-dev
# npm install electron-packager --save-dev # npm install electron-packager --save-dev
mkdir -p .package/dist || true mkdir -p .package/dist/src/ThirdParty || true
mkdir -p .package/src/ThirdParty || true
cp index.html .package cp index.html .package
cp electron/* .package cp electron/* .package
cp dist/engine.bundle.js .package/dist # The css files
cp dist/engineStyle.css .package/dist
cp dist/vendor.css .package/dist cp dist/vendor.css .package/dist
cp dist/engineStyle.bundle.js .package/dist cp main.css .package/main.css
cp dist/vendor.bundle.js .package/dist
# The js files.
cp dist/vendor.bundle.js .package/dist/vendor.bundle.js
cp main.bundle.js .package/main.bundle.js
cp src/ThirdParty/raphael.min.js .package/src/ThirdParty/raphael.min.js
npm run package-electron npm run package-electron

@ -211,7 +211,7 @@ function initAugmentations() {
name: AugmentationNames.Targeting3, name: AugmentationNames.Targeting3,
moneyCost: 1.15e8, moneyCost: 1.15e8,
repCost: 2.75e4, repCost: 2.75e4,
info: "The latest version of the 'Augmented Targeting' implant adds the ability to " + "lock-on and track threats.", info: "The latest version of the 'Augmented Targeting' implant adds the ability to lock-on and track threats.",
prereqs: [AugmentationNames.Targeting2], prereqs: [AugmentationNames.Targeting2],
dexterity_mult: 1.3, dexterity_mult: 1.3,
}); });

@ -16,8 +16,11 @@ class BitNode {
// BitNode number // BitNode number
number: number; number: number;
constructor(n: number, name: string, desc = "", info: JSX.Element = <></>) { difficulty: 0 | 1 | 2;
constructor(n: number, difficulty: 0 | 1 | 2, name: string, desc = "", info: JSX.Element = <></>) {
this.number = n; this.number = n;
this.difficulty = difficulty;
this.name = name; this.name = name;
this.desc = desc; this.desc = desc;
this.info = info; this.info = info;
@ -28,6 +31,7 @@ export const BitNodes: IMap<BitNode> = {};
BitNodes["BitNode1"] = new BitNode( BitNodes["BitNode1"] = new BitNode(
1, 1,
0,
"Source Genesis", "Source Genesis",
"The original BitNode", "The original BitNode",
( (
@ -54,6 +58,7 @@ BitNodes["BitNode1"] = new BitNode(
); );
BitNodes["BitNode2"] = new BitNode( BitNodes["BitNode2"] = new BitNode(
2, 2,
0,
"Rise of the Underworld", "Rise of the Underworld",
"From the shadows, they rose", //Gangs "From the shadows, they rose", //Gangs
( (
@ -101,6 +106,7 @@ BitNodes["BitNode2"] = new BitNode(
); );
BitNodes["BitNode3"] = new BitNode( BitNodes["BitNode3"] = new BitNode(
3, 3,
0,
"Corporatocracy", "Corporatocracy",
"The Price of Civilization", "The Price of Civilization",
( (
@ -140,6 +146,7 @@ BitNodes["BitNode3"] = new BitNode(
); );
BitNodes["BitNode4"] = new BitNode( BitNodes["BitNode4"] = new BitNode(
4, 4,
1,
"The Singularity", "The Singularity",
"The Man and the Machine", "The Man and the Machine",
( (
@ -164,6 +171,7 @@ BitNodes["BitNode4"] = new BitNode(
); );
BitNodes["BitNode5"] = new BitNode( BitNodes["BitNode5"] = new BitNode(
5, 5,
1,
"Artificial Intelligence", "Artificial Intelligence",
"Posthuman", "Posthuman",
( (
@ -211,6 +219,7 @@ BitNodes["BitNode5"] = new BitNode(
); );
BitNodes["BitNode6"] = new BitNode( BitNodes["BitNode6"] = new BitNode(
6, 6,
1,
"Bladeburners", "Bladeburners",
"Like Tears in Rain", "Like Tears in Rain",
( (
@ -255,6 +264,7 @@ BitNodes["BitNode6"] = new BitNode(
); );
BitNodes["BitNode7"] = new BitNode( BitNodes["BitNode7"] = new BitNode(
7, 7,
2,
"Bladeburners 2079", "Bladeburners 2079",
"More human than humans", "More human than humans",
( (
@ -303,6 +313,7 @@ BitNodes["BitNode7"] = new BitNode(
); );
BitNodes["BitNode8"] = new BitNode( BitNodes["BitNode8"] = new BitNode(
8, 8,
2,
"Ghost of Wall Street", "Ghost of Wall Street",
"Money never sleeps", "Money never sleeps",
( (
@ -347,6 +358,7 @@ BitNodes["BitNode8"] = new BitNode(
); );
BitNodes["BitNode9"] = new BitNode( BitNodes["BitNode9"] = new BitNode(
9, 9,
2,
"Hacktocracy", "Hacktocracy",
"Hacknet Unleashed", "Hacknet Unleashed",
( (
@ -389,6 +401,7 @@ BitNodes["BitNode9"] = new BitNode(
); );
BitNodes["BitNode10"] = new BitNode( BitNodes["BitNode10"] = new BitNode(
10, 10,
2,
"Digital Carbon", "Digital Carbon",
"Your body is not who you are", "Your body is not who you are",
( (
@ -428,6 +441,7 @@ BitNodes["BitNode10"] = new BitNode(
); );
BitNodes["BitNode11"] = new BitNode( BitNodes["BitNode11"] = new BitNode(
11, 11,
1,
"The Big Crash", "The Big Crash",
"Okay. Sell it all.", "Okay. Sell it all.",
( (
@ -492,6 +506,7 @@ BitNodes["BitNode11"] = new BitNode(
); );
BitNodes["BitNode12"] = new BitNode( BitNodes["BitNode12"] = new BitNode(
12, 12,
0,
"The Recursion", "The Recursion",
"Repeat.", "Repeat.",
( (
@ -507,18 +522,18 @@ BitNodes["BitNode12"] = new BitNode(
), ),
); );
// Books: Frontera, Shiner // Books: Frontera, Shiner
BitNodes["BitNode13"] = new BitNode(13, "fOS", "COMING SOON"); //Unlocks the new game mode and the rest of the BitNodes BitNodes["BitNode13"] = new BitNode(13, 2, "fOS", "COMING SOON"); //Unlocks the new game mode and the rest of the BitNodes
BitNodes["BitNode14"] = new BitNode(14, "", "COMING SOON"); BitNodes["BitNode14"] = new BitNode(14, 2, "", "COMING SOON");
BitNodes["BitNode15"] = new BitNode(15, "", "COMING SOON"); BitNodes["BitNode15"] = new BitNode(15, 2, "", "COMING SOON");
BitNodes["BitNode16"] = new BitNode(16, "", "COMING SOON"); BitNodes["BitNode16"] = new BitNode(16, 2, "", "COMING SOON");
BitNodes["BitNode17"] = new BitNode(17, "", "COMING SOON"); BitNodes["BitNode17"] = new BitNode(17, 2, "", "COMING SOON");
BitNodes["BitNode18"] = new BitNode(18, "", "COMING SOON"); BitNodes["BitNode18"] = new BitNode(18, 2, "", "COMING SOON");
BitNodes["BitNode19"] = new BitNode(19, "", "COMING SOON"); BitNodes["BitNode19"] = new BitNode(19, 2, "", "COMING SOON");
BitNodes["BitNode20"] = new BitNode(20, "", "COMING SOON"); BitNodes["BitNode20"] = new BitNode(20, 2, "", "COMING SOON");
BitNodes["BitNode21"] = new BitNode(21, "", "COMING SOON"); BitNodes["BitNode21"] = new BitNode(21, 2, "", "COMING SOON");
BitNodes["BitNode22"] = new BitNode(22, "", "COMING SOON"); BitNodes["BitNode22"] = new BitNode(22, 2, "", "COMING SOON");
BitNodes["BitNode23"] = new BitNode(23, "", "COMING SOON"); BitNodes["BitNode23"] = new BitNode(23, 2, "", "COMING SOON");
BitNodes["BitNode24"] = new BitNode(24, "", "COMING SOON"); BitNodes["BitNode24"] = new BitNode(24, 2, "", "COMING SOON");
export function initBitNodeMultipliers(p: IPlayer): void { export function initBitNodeMultipliers(p: IPlayer): void {
if (p.bitNodeN == null) { if (p.bitNodeN == null) {

@ -29,6 +29,9 @@ export function PortalPopup(props: IProps): React.ReactElement {
Source-File Level: {props.level} / {maxSourceFileLevel} Source-File Level: {props.level} / {maxSourceFileLevel}
<br /> <br />
<br /> <br />
Difficulty: {["easy", "normal", "hard"][bitNode.difficulty]}
<br />
<br />
{bitNode.info} {bitNode.info}
<br /> <br />
<br /> <br />

@ -3,6 +3,7 @@ import { removePopup } from "../../ui/React/createPopup";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { WorldMap } from "../../ui/React/WorldMap"; import { WorldMap } from "../../ui/React/WorldMap";
import { CityName } from "../../Locations/data/CityNames"; import { CityName } from "../../Locations/data/CityNames";
import { Settings } from "../../Settings/Settings";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -21,7 +22,15 @@ export function TravelPopup(props: IProps): React.ReactElement {
Travel to a different city for your Bladeburner activities. This does not cost any money. The city you are in Travel to a different city for your Bladeburner activities. This does not cost any money. The city you are in
for your Bladeburner duties does not affect your location in the game otherwise. for your Bladeburner duties does not affect your location in the game otherwise.
</p> </p>
<WorldMap currentCity={props.bladeburner.city as CityName} onTravel={(city: CityName) => travel(city)} /> {Settings.DisableASCIIArt ? (
Object.values(CityName).map((city: CityName) => (
<button key={city} className="std-button" onClick={() => travel(city)}>
{city}
</button>
))
) : (
<WorldMap currentCity={props.bladeburner.city as CityName} onTravel={(city: CityName) => travel(city)} />
)}
</> </>
); );
} }

@ -196,7 +196,7 @@ export class Roulette extends Game<IProps, IState> {
if (playerWin && Math.random() > 0.9) { if (playerWin && Math.random() > 0.9) {
playerWin = false; playerWin = false;
while (this.state.strategy.match(n)) { while (this.state.strategy.match(n)) {
n++; n = (n + 1) % 36;
} }
} }
if (playerWin) { if (playerWin) {

@ -1 +0,0 @@
export declare let cinematicTextFlag: boolean;

@ -1,119 +0,0 @@
import { Engine } from "./engine";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
import { createElement } from "../utils/uiHelpers/createElement";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { isString } from "../utils/helpers/isString";
export let cinematicTextFlag = false;
/**
* Print a message using a hacking-style "typing" effect.
* Note that this clears the UI so that the text from this is the only thing visible.
*
* @param lines {string[]} Array of strings to print, where each element is a separate line
*/
export function writeCinematicText(lines) {
cinematicTextFlag = true;
if (lines.constructor !== Array) {
throw new Error("Invalid non-array argument passed into writeCinematicText()");
}
// Reuse the 'Red Pill' content
Engine.loadCinematicTextContent();
const container = document.getElementById("cinematic-text-container");
container.style.width = "75%";
if (container == null) {
throw new Error("Could not find cinematic-text-container for writeCinematicText()");
}
removeChildrenFromElement(container);
for (let i = 0; i < lines.length; ++i) {
if (!isString(lines[i])) {
throw new Error("Invalid non-string element in 'lines' argument. writeCinematicText() failed");
}
}
return writeCinematicTextRecurse(lines)
.then(function () {
return cinematicTextEnd(); //Puts the continue button
})
.catch(function (e) {
exceptionAlert(e);
});
}
function writeCinematicTextRecurse(lines, lineNumber = 0) {
if (lineNumber >= lines.length) {
return Promise.resolve(true);
}
return writeCinematicTextLine(lines[lineNumber]).then(function () {
return writeCinematicTextRecurse(lines, lineNumber + 1);
});
}
function writeCinematicTextLine(line) {
return new Promise(function (resolve, reject) {
const container = document.getElementById("cinematic-text-container");
const pElem = document.createElement("p");
container.appendChild(pElem);
const promise = writeCinematicTextLetter(pElem, line, 0);
promise.then(
function (res) {
resolve(res);
},
function (e) {
reject(e);
},
);
});
}
function writeCinematicTextLetter(pElem, line, i = 0) {
return new Promise(function (resolve, reject) {
setTimeoutRef(function () {
const textToShow = line.substring(0, i);
if (i >= line.length) {
pElem.innerHTML = textToShow;
return resolve(true);
}
pElem.innerHTML = textToShow + "<span class='typed-cursor'> &#9608; </span>";
const promise = writeCinematicTextLetter(pElem, line, i + 1);
promise.then(
function (res) {
resolve(res);
},
function (e) {
reject(e);
},
);
}, 15);
});
}
function cinematicTextEnd() {
var container = document.getElementById("cinematic-text-container");
var mainMenu = document.getElementById("mainmenu-container");
container.appendChild(createElement("br"));
return new Promise(function (resolve) {
container.appendChild(
createElement("a", {
class: "a-link-button",
innerText: "Continue...",
clickListener: () => {
removeChildrenFromElement(container);
Engine.loadTerminalContent();
mainMenu.style.visibility = "visible";
cinematicTextFlag = false;
resolve();
},
}),
);
});
}

@ -1,7 +1,7 @@
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
// Array of all valid states // Array of all valid states
export const AllCorporationStates: string[] = ["START", "PURCHASE", "PRODUCTION", "SALE", "EXPORT"]; const AllCorporationStates: string[] = ["START", "PURCHASE", "PRODUCTION", "SALE", "EXPORT"];
export class CorporationState { export class CorporationState {
// Number representing what state the Corporation is in. The number // Number representing what state the Corporation is in. The number

@ -4,10 +4,12 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
import { removePopup } from "../../ui/React/createPopup"; import { removePopup } from "../../ui/React/createPopup";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../../utils/DialogBox";
import { IRouter } from "../../ui/Router";
interface IProps { interface IProps {
player: IPlayer; player: IPlayer;
popupId: string; popupId: string;
router: IRouter;
} }
export function CreateCorporationPopup(props: IProps): React.ReactElement { export function CreateCorporationPopup(props: IProps): React.ReactElement {
@ -40,6 +42,7 @@ export function CreateCorporationPopup(props: IProps): React.ReactElement {
"and manage your company in the City.", "and manage your company in the City.",
); );
removePopup(props.popupId); removePopup(props.popupId);
props.router.toCorporation();
} }
function seed(): void { function seed(): void {
@ -55,6 +58,7 @@ export function CreateCorporationPopup(props: IProps): React.ReactElement {
"You can visit and manage your company in the City.", "You can visit and manage your company in the City.",
); );
removePopup(props.popupId); removePopup(props.popupId);
props.router.toCorporation();
} }
return ( return (

@ -95,7 +95,7 @@ function BulkPurchase(props: IProps): React.ReactElement {
style={{ margin: "5px" }} style={{ margin: "5px" }}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
/> />
<button className="std-button">Confirm Bulk Purchase</button> <button className="std-button" onClick={bulkPurchase}>Confirm Bulk Purchase</button>
</> </>
); );
} }

@ -62,6 +62,7 @@ export function ResearchPopup(props: IProps): React.ReactElement {
Research(props.industry, allResearch[i]); Research(props.industry, allResearch[i]);
} catch (err) { } catch (err) {
dialogBoxCreate(err + ""); dialogBoxCreate(err + "");
return;
} }
dialogBoxCreate( dialogBoxCreate(

@ -24,35 +24,6 @@ export function checkIfConnectedToDarkweb(): void {
} }
} }
//Handler for dark web commands. The terminal's executeCommand() function will pass
//dark web-specific commands into this. It will pass in the raw split command array
//rather than the command string
export function executeDarkwebTerminalCommand(commandArray: string[]): void {
if (commandArray.length == 0) {
return;
}
switch (commandArray[0]) {
case "buy": {
if (commandArray.length != 2) {
Terminal.error("Incorrect number of arguments. Usage: ");
Terminal.print("buy -l");
Terminal.print("buy [item name]");
return;
}
const arg = commandArray[1];
if (arg == "-l" || arg == "-1" || arg == "--list") {
listAllDarkwebItems();
} else {
buyDarkwebItem(arg);
}
break;
}
default:
Terminal.error("Command not found");
break;
}
}
export function listAllDarkwebItems(): void { export function listAllDarkwebItems(): void {
for (const key in DarkWebItems) { for (const key in DarkWebItems) {
const item = DarkWebItems[key]; const item = DarkWebItems[key];

@ -22,7 +22,7 @@ export function TimeSkip(props: IProps): React.ReactElement {
return () => { return () => {
props.player.lastUpdate -= time; props.player.lastUpdate -= time;
props.engine._lastUpdate -= time; props.engine._lastUpdate -= time;
saveObject.saveGame(props.engine.indexedDb); saveObject.saveGame();
setTimeout(() => location.reload(), 1000); setTimeout(() => location.reload(), 1000);
}; };
} }

@ -20,7 +20,7 @@ interface IServerProps {
ip: string; ip: string;
} }
export function ServerAccordion(props: IServerProps): React.ReactElement { function ServerAccordion(props: IServerProps): React.ReactElement {
const server = AllServers[props.ip]; const server = AllServers[props.ip];
let totalSize = 0; let totalSize = 0;
for (const f of server.scripts) { for (const f of server.scripts) {

@ -1,11 +1,11 @@
import { Player } from "../Player"; import { Player } from "../Player";
import { Exploit } from "./Exploit"; import { Exploit } from "./Exploit";
(function () { export function startTampering(): void {
const a = 55; const a = 55;
setInterval(function () { setInterval(function () {
if (a.toExponential() !== "5.5e+1") { if (a.toExponential() !== "5.5e+1") {
Player.giveExploit(Exploit.PrototypeTampering); Player.giveExploit(Exploit.PrototypeTampering);
} }
}, 15 * 60 * 1000); // 15 minutes }, 15 * 60 * 1000); // 15 minutes
})(); }

@ -1,7 +1,7 @@
import { Player } from "../Player"; import { Player } from "../Player";
import { Exploit } from "./Exploit"; import { Exploit } from "./Exploit";
(function () { export function startUnclickable(): void {
function clickTheUnclickable(event: MouseEvent): void { function clickTheUnclickable(event: MouseEvent): void {
if (!event.target || !(event.target instanceof Element)) return; if (!event.target || !(event.target instanceof Element)) return;
const display = window.getComputedStyle(event.target as Element).display; const display = window.getComputedStyle(event.target as Element).display;
@ -19,4 +19,4 @@ import { Exploit } from "./Exploit";
} }
document.addEventListener("DOMContentLoaded", targetElement); document.addEventListener("DOMContentLoaded", targetElement);
})(); }

@ -1,360 +0,0 @@
import { IMap } from "../types";
/**
* Contains the "information" property for all the Factions, which is just a description of each faction
*/
export class FactionInfo {
/**
* The multiplier to apply to augmentation base purchase price.
*/
augmentationPriceMult: number;
/**
* The multiplier to apply to augmentation reputation base requirement.
*/
augmentationRepRequirementMult: number;
/**
* The names of all other factions considered to be enemies to this faction.
*/
enemies: string[];
/**
* The descriptive text to show on the faction's page.
*/
infoText: string;
/**
* A flag indicating if the faction supports field work to earn reputation.
*/
offerFieldWork: boolean;
/**
* A flag indicating if the faction supports hacking missions to earn reputation.
*/
offerHackingMission: boolean;
/**
* A flag indicating if the faction supports hacking work to earn reputation.
*/
offerHackingWork: boolean;
/**
* A flag indicating if the faction supports security work to earn reputation.
*/
offerSecurityWork: boolean;
constructor(
infoText: string,
enemies: string[],
offerHackingMission: boolean,
offerHackingWork: boolean,
offerFieldWork: boolean,
offerSecurityWork: boolean,
) {
this.infoText = infoText;
this.enemies = enemies;
this.offerHackingMission = offerHackingMission;
this.offerHackingWork = offerHackingWork;
this.offerFieldWork = offerFieldWork;
this.offerSecurityWork = offerSecurityWork;
// These are always all 1 for now.
this.augmentationPriceMult = 1;
this.augmentationRepRequirementMult = 1;
}
offersWork(): boolean {
return this.offerFieldWork || this.offerHackingMission || this.offerHackingWork || this.offerSecurityWork;
}
}
/**
* A map of all factions and associated info to them.
*/
// tslint:disable-next-line:variable-name
export const FactionInfos: IMap<FactionInfo> = {
// Endgame
Illuminati: new FactionInfo(
"Humanity never changes. No matter how civilized society becomes, it will eventually fall back into chaos. " +
"And from this chaos, we are the invisible hand that guides them to order. ",
[],
true,
true,
true,
false,
),
Daedalus: new FactionInfo(
"Yesterday we obeyed kings and bent our necks to emperors. Today we kneel only to truth.",
[],
true,
true,
true,
false,
),
"The Covenant": new FactionInfo(
"Surrender yourself. Give up your empty individuality to become part of something great, something eternal. " +
"Become a slave. Submit your mind, body, and soul. Only then can you set yourself free.<br>" +
"<br>" +
"Only then can you discover immortality.",
[],
true,
true,
true,
false,
),
// Megacorporations, each forms its own faction
ECorp: new FactionInfo(
"ECorp's mission is simple: to connect the world of today with the technology of tomorrow. With our wide " +
"range of Internet-related software and commercial hardware, ECorp makes the world's information " +
"universally accessible.",
[],
true,
true,
true,
true,
),
MegaCorp: new FactionInfo(
"MegaCorp does what no other dares to do. We imagine. We create. We invent. We create what others have " +
"never even dreamed of. Our work fills the world's needs for food, water, power, and transporation on an " +
"unprecendented scale, in ways that no other company can.<br>" +
"<br>" +
"In our labs and factories and on the ground with customers, MegaCorp is ushering in a new era for the world.",
[],
true,
true,
true,
true,
),
"Bachman & Associates": new FactionInfo(
"Where Law and Business meet - thats where we are.<br>" +
"<br>" +
"Legal Insight - Business Instinct - Innovative Experience.",
[],
true,
true,
true,
true,
),
"Blade Industries": new FactionInfo("Augmentation is Salvation.", [], true, true, true, true),
NWO: new FactionInfo(
"Humans don't truly desire freedom. They want to be observed, understood, and judged. They want to " +
"be given purpose and direction in life. That is why they created God. And that is why they created " +
"civilization - not because of willingness, but because of a need to be incorporated into higher orders of " +
"structure and meaning.",
[],
true,
true,
true,
true,
),
"Clarke Incorporated": new FactionInfo("The Power of the Genome - Unlocked.", [], true, true, true, true),
"OmniTek Incorporated": new FactionInfo(
"Simply put, our mission is to design and build robots that make a difference.",
[],
true,
true,
true,
true,
),
"Four Sigma": new FactionInfo(
"The scientific method is the best way to approach investing. Big strategies backed up with big data. Driven " +
"by deep learning and innovative ideas. And improved by iteration. That's Four Sigma.",
[],
true,
true,
true,
true,
),
"KuaiGong International": new FactionInfo("Dream big. Work hard. Make history.", [], true, true, true, true),
// Other Corporations
"Fulcrum Secret Technologies": new FactionInfo(
"The human organism has an innate desire to worship. That is why they created gods. If there were no gods, it " +
"would be necessary to create them. And now we can.",
[],
true,
true,
false,
true,
),
// Hacker groups
BitRunners: new FactionInfo(
"Our entire lives are controlled by bits. All of our actions, our thoughts, our personal information. It's " +
"all transformed into bits, stored in bits, communicated through bits. Its impossible for any person to move, " +
"to live, to operate at any level without the use of bits. And when a person moves, lives, and operates, they " +
"leave behind their bits, mere traces of seemingly meaningless fragments of information. But these bits can be " +
"reconstructed. Transformed. Used.<br>" +
"<br>" +
"Those who run the bits, run the world.",
[],
true,
true,
false,
false,
),
"The Black Hand": new FactionInfo(
"The world, so afraid of strong government, now has no government. Only power - Digital power. Financial " +
"power. Technological power. And those at the top rule with an invisible hand. They built a society where the " +
"rich get richer, and everyone else suffers.<br>" +
"<br>" +
"So much pain. So many lives. Their darkness must end.",
[],
true,
true,
true,
false,
),
NiteSec: new FactionInfo(
" __..__ <br>" +
" _.nITESECNIt. <br>" +
" .-'NITESECNITESEc. <br>" +
" .' NITESECNITESECn <br>" +
" / NITESECNITESEC; <br>" +
" : :NITESECNITESEC; <br>" +
" ; $ NITESECNITESECN <br>" +
" : _, ,N'ITESECNITESEC <br>" +
" : .+^^`, : `NITESECNIT <br>" +
" ) /), `-,-=,NITESECNI <br>" +
" / ^ ,-;|NITESECN; <br>" +
" / _.' '-';NITESECN <br>" +
" ( , ,-''`^NITE' <br>" +
" )` :`. .' <br>" +
" )-- ; `- / <br>" +
" ' _.-' : <br>" +
" ( _.-' . <br>" +
" ------. <br>" +
" . <br>" +
" _.nIt <br>" +
" _.nITESECNi <br>" +
" nITESECNIT^' <br>" +
" NITE^' ___ <br>" +
" / .gP''''Tp. <br>" +
" : d' . `b <br>" +
" ; d' o `b ; <br>" +
" / d; `b| <br>" +
" /, $; @ `: <br>" +
" /' $$ ; <br>" +
" .' $$b o | <br>" +
" .' d$$$; : <br>" +
" / .d$$$$; , ; <br>" +
" d .dNITESEC $ | <br>" +
" :bp.__.gNITESEC$$ :$ ; <br>" +
" NITESECNITESECNIT $$b : <br>",
[],
true,
true,
false,
false,
),
// City factions, essentially governments
Aevum: new FactionInfo("The Silicon City.", ["Chongqing", "New Tokyo", "Ishima", "Volhaven"], true, true, true, true),
Chongqing: new FactionInfo("Serve the People.", ["Sector-12", "Aevum", "Volhaven"], true, true, true, true),
Ishima: new FactionInfo(
"The East Asian Order of the Future.",
["Sector-12", "Aevum", "Volhaven"],
true,
true,
true,
true,
),
"New Tokyo": new FactionInfo("Asia's World City.", ["Sector-12", "Aevum", "Volhaven"], true, true, true, true),
"Sector-12": new FactionInfo(
"The City of the Future.",
["Chongqing", "New Tokyo", "Ishima", "Volhaven"],
true,
true,
true,
true,
),
Volhaven: new FactionInfo(
"Benefit, Honor, and Glory.",
["Chongqing", "Sector-12", "New Tokyo", "Aevum", "Ishima"],
true,
true,
true,
true,
),
// Criminal Organizations/Gangs
"Speakers for the Dead": new FactionInfo(
"It is better to reign in Hell than to serve in Heaven.",
[],
true,
true,
true,
true,
),
"The Dark Army": new FactionInfo(
"The World doesn't care about right or wrong. It only cares about power.",
[],
true,
true,
true,
false,
),
"The Syndicate": new FactionInfo("Honor holds you back.", [], true, true, true, true),
Silhouette: new FactionInfo(
"Corporations have filled the void of power left behind by the collapse of Western government. The issue is " +
"they've become so big that you don't know who they're working for. And if you're employed at one of these " +
"corporations, you don't even know who you're working for.<br>" +
"<br>" +
"That's terror. Terror, fear, and corruption. All born into the system, all propagated by the system.",
[],
true,
true,
true,
false,
),
Tetrads: new FactionInfo("Following the mandate of Heaven and carrying out the way.", [], false, false, true, true),
"Slum Snakes": new FactionInfo("Slum Snakes rule!", [], false, false, true, true),
// Earlygame factions - factions the player will prestige with early on that don't belong in other categories.
Netburners: new FactionInfo("~~//*>H4CK||3T 8URN3R5**>?>\\~~", [], true, true, false, false),
"Tian Di Hui": new FactionInfo("Obey Heaven and work righteously.", [], true, true, false, true),
CyberSec: new FactionInfo(
"The Internet is the first thing that was built that we don't fully understand, the largest " +
"experiment in anarchy that we have ever had. And as the world becomes increasingly dominated by it, " +
"society approaches the brink of total chaos. We serve only to protect society, to protect humanity, to " +
"protect the world from imminent collapse.",
[],
true,
true,
false,
false,
),
// Special Factions
Bladeburners: new FactionInfo(
"It's too bad they won't live. But then again, who does?<br><br>Note that for this faction, reputation can " +
"only be gained through Bladeburner actions. Completing Bladeburner contracts/operations will increase your " +
"reputation.",
[],
false,
false,
false,
false,
),
};

433
src/Faction/FactionInfo.tsx Normal file

@ -0,0 +1,433 @@
import React from "react";
import { IMap } from "../types";
/**
* Contains the "information" property for all the Factions, which is just a description of each faction
*/
export class FactionInfo {
/**
* The multiplier to apply to augmentation base purchase price.
*/
augmentationPriceMult: number;
/**
* The multiplier to apply to augmentation reputation base requirement.
*/
augmentationRepRequirementMult: number;
/**
* The names of all other factions considered to be enemies to this faction.
*/
enemies: string[];
/**
* The descriptive text to show on the faction's page.
*/
infoText: JSX.Element;
/**
* A flag indicating if the faction supports field work to earn reputation.
*/
offerFieldWork: boolean;
/**
* A flag indicating if the faction supports hacking missions to earn reputation.
*/
offerHackingMission: boolean;
/**
* A flag indicating if the faction supports hacking work to earn reputation.
*/
offerHackingWork: boolean;
/**
* A flag indicating if the faction supports security work to earn reputation.
*/
offerSecurityWork: boolean;
constructor(
infoText: JSX.Element,
enemies: string[],
offerHackingMission: boolean,
offerHackingWork: boolean,
offerFieldWork: boolean,
offerSecurityWork: boolean,
) {
this.infoText = infoText;
this.enemies = enemies;
this.offerHackingMission = offerHackingMission;
this.offerHackingWork = offerHackingWork;
this.offerFieldWork = offerFieldWork;
this.offerSecurityWork = offerSecurityWork;
// These are always all 1 for now.
this.augmentationPriceMult = 1;
this.augmentationRepRequirementMult = 1;
}
offersWork(): boolean {
return this.offerFieldWork || this.offerHackingMission || this.offerHackingWork || this.offerSecurityWork;
}
}
/**
* A map of all factions and associated info to them.
*/
// tslint:disable-next-line:variable-name
export const FactionInfos: IMap<FactionInfo> = {
// Endgame
Illuminati: new FactionInfo(
(
<>
Humanity never changes. No matter how civilized society becomes, it will eventually fall back into chaos. And
from this chaos, we are the invisible hand that guides them to order.{" "}
</>
),
[],
true,
true,
true,
false,
),
Daedalus: new FactionInfo(
<>Yesterday we obeyed kings and bent our necks to emperors. Today we kneel only to truth.</>,
[],
true,
true,
true,
false,
),
"The Covenant": new FactionInfo(
(
<>
Surrender yourself. Give up your empty individuality to become part of something great, something eternal.
Become a slave. Submit your mind, body, and soul. Only then can you set yourself free.
<br />
<br />
Only then can you discover immortality.
</>
),
[],
true,
true,
true,
false,
),
// Megacorporations, each forms its own faction
ECorp: new FactionInfo(
(
<>
ECorp's mission is simple: to connect the world of today with the technology of tomorrow. With our wide range of
Internet-related software and commercial hardware, ECorp makes the world's information universally accessible.
</>
),
[],
true,
true,
true,
true,
),
MegaCorp: new FactionInfo(
(
<>
MegaCorp does what no other dares to do. We imagine. We create. We invent. We create what others have never even
dreamed of. Our work fills the world's needs for food, water, power, and transporation on an unprecendented
scale, in ways that no other company can.
<br />
<br />
In our labs and factories and on the ground with customers, MegaCorp is ushering in a new era for the world.
</>
),
[],
true,
true,
true,
true,
),
"Bachman & Associates": new FactionInfo(
(
<>
Where Law and Business meet - thats where we are.
<br />
<br />
Legal Insight - Business Instinct - Innovative Experience.
</>
),
[],
true,
true,
true,
true,
),
"Blade Industries": new FactionInfo(<>Augmentation is Salvation.</>, [], true, true, true, true),
NWO: new FactionInfo(
(
<>
Humans don't truly desire freedom. They want to be observed, understood, and judged. They want to be given
purpose and direction in life. That is why they created God. And that is why they created civilization - not
because of willingness, but because of a need to be incorporated into higher orders of structure and meaning.
</>
),
[],
true,
true,
true,
true,
),
"Clarke Incorporated": new FactionInfo(<>The Power of the Genome - Unlocked.</>, [], true, true, true, true),
"OmniTek Incorporated": new FactionInfo(
<>Simply put, our mission is to design and build robots that make a difference.</>,
[],
true,
true,
true,
true,
),
"Four Sigma": new FactionInfo(
(
<>
The scientific method is the best way to approach investing. Big strategies backed up with big data. Driven by
deep learning and innovative ideas. And improved by iteration. That's Four Sigma.
</>
),
[],
true,
true,
true,
true,
),
"KuaiGong International": new FactionInfo(<>Dream big. Work hard. Make history.</>, [], true, true, true, true),
// Other Corporations
"Fulcrum Secret Technologies": new FactionInfo(
(
<>
The human organism has an innate desire to worship. That is why they created gods. If there were no gods, it
would be necessary to create them. And now we can.
</>
),
[],
true,
true,
false,
true,
),
// Hacker groups
BitRunners: new FactionInfo(
(
<>
Our entire lives are controlled by bits. All of our actions, our thoughts, our personal information. It's all
transformed into bits, stored in bits, communicated through bits. Its impossible for any person to move, to
live, to operate at any level without the use of bits. And when a person moves, lives, and operates, they leave
behind their bits, mere traces of seemingly meaningless fragments of information. But these bits can be
reconstructed. Transformed. Used.
<br />
<br />
Those who run the bits, run the world.
</>
),
[],
true,
true,
false,
false,
),
"The Black Hand": new FactionInfo(
(
<>
The world, so afraid of strong government, now has no government. Only power - Digital power. Financial power.
Technological power. And those at the top rule with an invisible hand. They built a society where the rich get
richer, and everyone else suffers.
<br />
<br />
So much pain. So many lives. Their darkness must end.
</>
),
[],
true,
true,
true,
false,
),
// prettier-ignore
NiteSec: new FactionInfo(<>
{" __..__ "}<br />
{" _.nITESECNIt. "}<br />
{" .-'NITESECNITESEc. "}<br />
{" .' NITESECNITESECn "}<br />
{" / NITESECNITESEC; "}<br />
{" : :NITESECNITESEC; "}<br />
{" ; $ NITESECNITESECN "}<br />
{" : _, ,N'ITESECNITESEC "}<br />
{" : .+^^`, : `NITESECNIT "}<br />
{" ) /), `-,-=,NITESECNI "}<br />
{" / ^ ,-;|NITESECN; "}<br />
{" / _.' '-';NITESECN "}<br />
{" ( , ,-''`^NITE' "}<br />
{" )` :`. .' "}<br />
{" )-- ; `- / "}<br />
{" ' _.-' : "}<br />
{" ( _.-' . "}<br />
{" ------. "}<br />
{" . "}<br />
{" _.nIt "}<br />
{" _.nITESECNi "}<br />
{" nITESECNIT^' "}<br />
{" NITE^' ___ "}<br />
{" / .gP''''Tp. "}<br />
{" : d' . `b "}<br />
{" ; d' o `b ; "}<br />
{" / d; `b| "}<br />
{" /, $; @ `: "}<br />
{" /' $$ ; "}<br />
{" .' $$b o | "}<br />
{" .' d$$$; : "}<br />
{" / .d$$$$; , ; "}<br />
{" d .dNITESEC $ | "}<br />
{" :bp.__.gNITESEC$$ :$ ; "}<br />
{" NITESECNITESECNIT $$b : "}<br /></>,
[],
true,
true,
false,
false,
),
// City factions, essentially governments
Aevum: new FactionInfo(
<>The Silicon City.</>,
["Chongqing", "New Tokyo", "Ishima", "Volhaven"],
true,
true,
true,
true,
),
Chongqing: new FactionInfo(<>Serve the People.</>, ["Sector-12", "Aevum", "Volhaven"], true, true, true, true),
Ishima: new FactionInfo(
<>The East Asian Order of the Future.</>,
["Sector-12", "Aevum", "Volhaven"],
true,
true,
true,
true,
),
"New Tokyo": new FactionInfo(<>Asia's World City.</>, ["Sector-12", "Aevum", "Volhaven"], true, true, true, true),
"Sector-12": new FactionInfo(
<>The City of the Future.</>,
["Chongqing", "New Tokyo", "Ishima", "Volhaven"],
true,
true,
true,
true,
),
Volhaven: new FactionInfo(
<>Benefit, Honor, and Glory.</>,
["Chongqing", "Sector-12", "New Tokyo", "Aevum", "Ishima"],
true,
true,
true,
true,
),
// Criminal Organizations/Gangs
"Speakers for the Dead": new FactionInfo(
<>It is better to reign in Hell than to serve in Heaven.</>,
[],
true,
true,
true,
true,
),
"The Dark Army": new FactionInfo(
<>The World doesn't care about right or wrong. It only cares about power.</>,
[],
true,
true,
true,
false,
),
"The Syndicate": new FactionInfo(<>Honor holds you back.</>, [], true, true, true, true),
Silhouette: new FactionInfo(
(
<>
Corporations have filled the void of power left behind by the collapse of Western government. The issue is
they've become so big that you don't know who they're working for. And if you're employed at one of these
corporations, you don't even know who you're working for.
<br />
<br />
That's terror. Terror, fear, and corruption. All born into the system, all propagated by the system.
</>
),
[],
true,
true,
true,
false,
),
Tetrads: new FactionInfo(
<>Following the mandate of Heaven and carrying out the way.</>,
[],
false,
false,
true,
true,
),
"Slum Snakes": new FactionInfo(<>Slum Snakes rule!</>, [], false, false, true, true),
// Earlygame factions - factions the player will prestige with early on that don't belong in other categories.
Netburners: new FactionInfo(<>{"~~//*>H4CK||3T 8URN3R5**>?>\\~~"}</>, [], true, true, false, false),
"Tian Di Hui": new FactionInfo(<>Obey Heaven and work righteously.</>, [], true, true, false, true),
CyberSec: new FactionInfo(
(
<>
The Internet is the first thing that was built that we don't fully understand, the largest experiment in anarchy
that we have ever had. And as the world becomes increasingly dominated by it, society approaches the brink of
total chaos. We serve only to protect society, to protect humanity, to protect the world from imminent collapse.
</>
),
[],
true,
true,
false,
false,
),
// Special Factions
Bladeburners: new FactionInfo(
(
<>
It's too bad they won't live. But then again, who does?
<br />
<br />
Note that for this faction, reputation can only be gained through Bladeburner actions. Completing Bladeburner
contracts/operations will increase your reputation.
</>
),
[],
false,
false,
false,
false,
),
};

@ -14,6 +14,11 @@ import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import TableBody from "@mui/material/TableBody";
import Table from "@mui/material/Table";
type IProps = { type IProps = {
faction: Faction; faction: Faction;
routeToMainPage: () => void; routeToMainPage: () => void;
@ -104,8 +109,17 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
(!player.augmentations.some((a) => a.name === aug) && !player.queuedAugmentations.some((a) => a.name === aug)), (!player.augmentations.some((a) => a.name === aug) && !player.queuedAugmentations.some((a) => a.name === aug)),
); );
const purchaseableAugmentation = (aug: string): React.ReactNode => { const purchaseableAugmentation = (aug: string, owned = false): React.ReactNode => {
return <PurchaseableAugmentation augName={aug} faction={props.faction} key={aug} p={player} rerender={rerender} />; return (
<PurchaseableAugmentation
augName={aug}
faction={props.faction}
key={aug}
p={player}
rerender={rerender}
owned={owned}
/>
);
}; };
const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug)); const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug));
@ -116,42 +130,33 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
ownedElem = ( ownedElem = (
<> <>
<br /> <br />
<h2>Purchased Augmentations</h2> <Typography variant="h4">Purchased Augmentations</Typography>
<p>This factions also offers these augmentations but you already own them.</p> <Typography>This factions also offers these augmentations but you already own them.</Typography>
{owned.map((aug) => purchaseableAugmentation(aug))} {owned.map((aug) => purchaseableAugmentation(aug, true))}
</> </>
); );
} }
return ( return (
<div> <div>
<StdButton onClick={props.routeToMainPage} text={"Back"} /> <Button onClick={props.routeToMainPage}>Back</Button>
<h1>Faction Augmentations</h1> <Typography variant="h4">Faction Augmentations</Typography>
<p> <Typography>
These are all of the Augmentations that are available to purchase from {props.faction.name}. Augmentations are These are all of the Augmentations that are available to purchase from {props.faction.name}. Augmentations are
powerful upgrades that will enhance your abilities. powerful upgrades that will enhance your abilities.
</p> </Typography>
<StdButton onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)} text={"Sort by Cost"} /> <Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>
<StdButton <Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}>Sort by Reputation</Button>
onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)} <Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}>Sort by Default Order</Button>
text={"Sort by Reputation"}
/>
<StdButton
onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}
text={"Sort by Default Order"}
/>
<br />
{augListElems}
{ownedElem}
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br /> <br />
<Table size="small" padding="none">
<TableBody>{augListElems}</TableBody>
</Table>
<Table size="small" padding="none">
<TableBody>{ownedElem}</TableBody>
</Table>
</div> </div>
); );
} }

@ -7,6 +7,7 @@ import { CONSTANTS } from "../../Constants";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { repFromDonation } from "../formulas/donation"; import { repFromDonation } from "../formulas/donation";
import { Favor } from "../../ui/React/Favor";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation"; import { Reputation } from "../../ui/React/Reputation";
@ -18,6 +19,11 @@ import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../../utils/DialogBox";
import { MathComponent } from "mathjax-react"; import { MathComponent } from "mathjax-react";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
type IProps = { type IProps = {
faction: Faction; faction: Faction;
disabled: boolean; disabled: boolean;
@ -67,36 +73,45 @@ export function DonateOption(props: IProps): React.ReactElement {
function Status(): React.ReactElement { function Status(): React.ReactElement {
if (donateAmt === null) return <></>; if (donateAmt === null) return <></>;
if (!canDonate()) { if (!canDonate()) {
if (props.p.money.lt(donateAmt)) return <p>Insufficient funds</p>; if (props.p.money.lt(donateAmt)) return <Typography>Insufficient funds</Typography>;
return <p>Invalid donate amount entered!</p>; return <Typography>Invalid donate amount entered!</Typography>;
} }
return <p>This donation will result in {Reputation(repFromDonation(donateAmt, props.p))} reputation gain</p>; return (
<Typography>
This donation will result in {Reputation(repFromDonation(donateAmt, props.p))} reputation gain
</Typography>
);
} }
return ( return (
<div className={"faction-work-div"}> <Paper sx={{ my: 1, p: 1, width: "100%" }}>
<div className={"faction-work-div-wrapper"}> <Status />
<input {props.disabled ? (
className="text-input" <Typography>
onChange={onChange} Unlock donations at {Favor(props.favorToDonate)} favor with {props.faction.name}
placeholder={"Donation amount"} </Typography>
style={inputStyleMarkup} ) : (
disabled={props.disabled} <>
/> <TextField
<StdButton onClick={donate} text={"Donate Money"} disabled={props.disabled || !canDonate()} /> variant="standard"
<Status /> onChange={onChange}
{props.disabled ? ( placeholder={"Donation amount"}
<p> disabled={props.disabled}
Unlocked at {props.favorToDonate} favor with {props.faction.name} InputProps={{
</p> endAdornment: (
) : ( <Button onClick={donate} disabled={props.disabled || !canDonate()}>
<div className="text"> donate
</Button>
),
}}
/>
<Typography>
<MathComponent <MathComponent
tex={String.raw`reputation = \frac{\text{donation amount} \times \text{reputation multiplier}}{10^{${digits}}}`} tex={String.raw`reputation = \frac{\text{donation amount} \times \text{reputation multiplier}}{10^{${digits}}}`}
/> />
</div> </Typography>
)} </>
</div> )}
</div> </Paper>
); );
} }

@ -21,6 +21,9 @@ import { createPopup } from "../../ui/React/createPopup";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { CreateGangPopup } from "./CreateGangPopup"; import { CreateGangPopup } from "./CreateGangPopup";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
type IProps = { type IProps = {
faction: Faction; faction: Faction;
}; };
@ -145,8 +148,11 @@ export function FactionRoot(props: IProps): React.ReactElement {
} }
return ( return (
<div className="faction-container"> <>
<h1>{faction.name}</h1> <Button onClick={() => router.toFactions()}>Back</Button>
<Typography variant="h4" color="primary">
{faction.name}
</Typography>
<Info faction={faction} factionInfo={factionInfo} /> <Info faction={faction} factionInfo={factionInfo} />
{canAccessGang && <Option buttonText={"Manage Gang"} infoText={gangInfo} onClick={() => manageGang(faction)} />} {canAccessGang && <Option buttonText={"Manage Gang"} infoText={gangInfo} onClick={() => manageGang(faction)} />}
{!isPlayersGang && factionInfo.offerHackingMission && ( {!isPlayersGang && factionInfo.offerHackingMission && (
@ -186,7 +192,7 @@ export function FactionRoot(props: IProps): React.ReactElement {
onClick={sleevePurchases} onClick={sleevePurchases}
/> />
)} )}
</div> </>
); );
} }

@ -13,85 +13,79 @@ import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor"; import { Favor } from "../../ui/React/Favor";
import { MathComponent } from "mathjax-react"; import { MathComponent } from "mathjax-react";
import { Theme } from "@mui/material/styles";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
type IProps = { type IProps = {
faction: Faction; faction: Faction;
factionInfo: FactionInfo; factionInfo: FactionInfo;
}; };
type IInnerHTMLMarkup = { const useStyles = makeStyles((theme: Theme) =>
__html: string; createStyles({
}; noformat: {
whiteSpace: "pre-wrap",
},
}),
);
const blockStyleMarkup = { export function Info(props: IProps): React.ReactElement {
display: "block", const classes = useStyles();
};
const infoStyleMarkup = { const favorGain = props.faction.getFavorGain()[0];
display: "block", return (
width: "70%", <>
}; <Typography classes={{ root: classes.noformat }}>{props.factionInfo.infoText}</Typography>
<Typography>-------------------------</Typography>
<Box display="flex">
<Tooltip
title={
<>
<Typography>
You will have {Favor(props.faction.favor + favorGain)} faction favor after installing an Augmentation.
</Typography>
<MathComponent tex={String.raw`\large{r = \text{total faction reputation}}`} />
<MathComponent
tex={String.raw`\large{favor=\left\lfloor\log_{1.02}\left(\frac{r+25000}{25500}\right)\right\rfloor}`}
/>
</>
}
>
<Typography>Reputation: {Reputation(props.faction.playerReputation)}</Typography>
</Tooltip>
</Box>
export class Info extends React.Component<IProps, any> { <Typography>-------------------------</Typography>
constructor(props: IProps) {
super(props);
this.getFavorGainContent = this.getFavorGainContent.bind(this); <Box display="flex">
this.getReputationContent = this.getReputationContent.bind(this); <Tooltip
} title={
<>
<Typography>
Faction favor increases the rate at which you earn reputation for this faction by 1% per favor. Faction
favor is gained whenever you install an Augmentation. The amount of favor you gain depends on the total
amount of reputation you earned with this faction. Across all resets.
</Typography>
<MathComponent tex={String.raw`\large{r = reputation}`} />
<MathComponent tex={String.raw`\large{\Delta r = \Delta r \times \frac{100+favor}{100}}`} />
</>
}
>
<Typography>Faction Favor: {Favor(props.faction.favor)}</Typography>
</Tooltip>
</Box>
getFavorGainContent(): JSX.Element { <Typography>-------------------------</Typography>
const favorGain = this.props.faction.getFavorGain()[0]; <Typography>
return ( Perform work/carry out assignments for your faction to help further its cause! By doing so you will earn
<> reputation for your faction. You will also gain reputation passively over time, although at a very slow rate.
You will have {Favor(this.props.faction.favor + favorGain)} faction favor after installing an Augmentation. Earning reputation will allow you to purchase Augmentations through this faction, which are powerful upgrades
<MathComponent tex={String.raw`\large{r = \text{total faction reputation}}`} /> that enhance your abilities.
<MathComponent </Typography>
tex={String.raw`\large{favor=\left\lfloor\log_{1.02}\left(\frac{r+25000}{25500}\right)\right\rfloor}`} </>
/> );
</>
);
}
getReputationContent(): JSX.Element {
return <>Reputation: {Reputation(this.props.faction.playerReputation)}</>;
}
render(): React.ReactNode {
const favorTooltip = (
<>
Faction favor increases the rate at which you earn reputation for this faction by 1% per favor. Faction favor is
gained whenever you install an Augmentation. The amount of favor you gain depends on the total amount of
reputation you earned with this faction. Across all resets.
<MathComponent tex={String.raw`\large{r = reputation}`} />
<MathComponent tex={String.raw`\large{\Delta r = \Delta r \times \frac{100+favor}{100}}`} />
</>
);
const infoText: IInnerHTMLMarkup = {
__html: this.props.factionInfo.infoText,
};
return (
<div>
<pre>
<i className={"text"} dangerouslySetInnerHTML={infoText}></i>
</pre>
<p style={blockStyleMarkup}>-------------------------</p>
<AutoupdatingParagraph
intervalTime={5e3}
getContent={this.getReputationContent}
getTooltip={this.getFavorGainContent}
/>
<p style={blockStyleMarkup}>-------------------------</p>
<ParagraphWithTooltip content={<>Faction Favor: {Favor(this.props.faction.favor)}</>} tooltip={favorTooltip} />
<p style={blockStyleMarkup}>-------------------------</p>
<p style={infoStyleMarkup}>
Perform work/carry out assignments for your faction to help further its cause! By doing so you will earn
reputation for your faction. You will also gain reputation passively over time, although at a very slow rate.
Earning reputation will allow you to purchase Augmentations through this faction, which are powerful upgrades
that enhance your abilities.
</p>
</div>
);
}
} }

@ -7,21 +7,24 @@ import * as React from "react";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
import Box from "@mui/material/Box";
type IProps = { type IProps = {
buttonText: string; buttonText: string;
infoText: string; infoText: string;
onClick: (e: React.MouseEvent<HTMLElement>) => void; onClick: (e: React.MouseEvent<HTMLElement>) => void;
}; };
export class Option extends React.Component<IProps, any> { export function Option(props: IProps): React.ReactElement {
render(): React.ReactNode { return (
return ( <Box>
<div className={"faction-work-div"}> <Paper sx={{ my: 1, p: 1, width: "100%" }}>
<div className={"faction-work-div-wrapper"}> <Button onClick={props.onClick}>{props.buttonText}</Button>
<StdButton onClick={this.props.onClick} text={this.props.buttonText} /> <Typography>{props.infoText}</Typography>
<p>{this.props.infoText}</p> </Paper>
</div> </Box>
</div> );
);
}
} }

@ -19,13 +19,60 @@ import { IMap } from "../../types";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import { Augmentation as AugFormat } from "../../ui/React/Augmentation"; import { Augmentation as AugFormat } from "../../ui/React/Augmentation";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import { TableCell } from "../../ui/React/Table";
import TableRow from "@mui/material/TableRow";
type IProps = { interface IReqProps {
augName: string;
p: IPlayer;
hasReq: boolean;
rep: number;
hasRep: boolean;
cost: number;
hasCost: boolean;
}
function Requirements(props: IReqProps): React.ReactElement {
const aug = Augmentations[props.augName];
if (!props.hasReq) {
return (
<TableCell key={1} colSpan={2}>
<Typography color="error">
Requires{" "}
{aug.prereqs.map((aug, i) => (
<AugFormat key={i} name={aug} />
))}
</Typography>
</TableCell>
);
}
let color = !props.hasRep || !props.hasCost ? "error" : "primary";
return (
<React.Fragment key="f">
<TableCell key={1}>
<Typography color={color}>
<Money money={props.cost} player={props.p} />
</Typography>
</TableCell>
<TableCell key={2}>
<Typography color={color}>Requires {Reputation(props.rep)} faction reputation</Typography>
</TableCell>
</React.Fragment>
);
}
interface IProps {
augName: string; augName: string;
faction: Faction; faction: Faction;
p: IPlayer; p: IPlayer;
rerender: () => void; rerender: () => void;
}; owned?: boolean;
}
export function PurchaseableAugmentation(props: IProps): React.ReactElement { export function PurchaseableAugmentation(props: IProps): React.ReactElement {
const aug = Augmentations[props.augName]; const aug = Augmentations[props.augName];
@ -39,22 +86,6 @@ export function PurchaseableAugmentation(props: IProps): React.ReactElement {
return aug.baseRepRequirement * props.faction.getInfo().augmentationRepRequirementMult; return aug.baseRepRequirement * props.faction.getInfo().augmentationRepRequirementMult;
} }
function handleClick(): void {
if (!Settings.SuppressBuyAugmentationConfirmation) {
const popupId = "purchase-augmentation-popup";
createPopup(popupId, PurchaseAugmentationPopup, {
aug: aug,
faction: props.faction,
player: props.p,
rerender: props.rerender,
popupId: popupId,
});
} else {
purchaseAugmentation(aug, props.faction);
props.rerender();
}
}
// Whether the player has the prerequisite Augmentations // Whether the player has the prerequisite Augmentations
function hasPrereqs(): boolean { function hasPrereqs(): boolean {
return hasAugmentationPrereqs(aug); return hasAugmentationPrereqs(aug);
@ -94,39 +125,12 @@ export function PurchaseableAugmentation(props: IProps): React.ReactElement {
const moneyCost = getMoneyCost(); const moneyCost = getMoneyCost();
const repCost = getRepCost(); const repCost = getRepCost();
const hasReq = hasPrereqs();
const hasRep = hasReputation();
const hasCost = aug.baseCost !== 0 && props.p.money.gt(aug.baseCost * props.faction.getInfo().augmentationPriceMult);
// Determine UI properties // Determine UI properties
let disabled = false; const color: "error" | "primary" = !hasReq || !hasRep || !hasCost ? "error" : "primary";
let status: JSX.Element = <></>;
let color = "";
if (!hasPrereqs()) {
disabled = true;
status = <>LOCKED (Requires {aug.prereqs.map((aug) => AugFormat(aug))} as prerequisite)</>;
color = "red";
} else if (aug.name !== AugmentationNames.NeuroFluxGovernor && (aug.owned || owned())) {
disabled = true;
} else if (hasReputation()) {
status = (
<>
UNLOCKED (at {Reputation(repCost)} faction reputation) - <Money money={moneyCost} player={props.p} />
</>
);
} else {
disabled = true;
status = (
<>
LOCKED (Requires {Reputation(repCost)} faction reputation - <Money money={moneyCost} player={props.p} />)
</>
);
color = "red";
}
const txtStyle: IMap<string> = {
display: "inline-block",
};
if (color !== "") {
txtStyle.color = color;
}
// Determine button txt // Determine button txt
let btnTxt = aug.name; let btnTxt = aug.name;
@ -135,16 +139,16 @@ export function PurchaseableAugmentation(props: IProps): React.ReactElement {
} }
let tooltip = <></>; let tooltip = <></>;
if (typeof aug.info === "string") if (typeof aug.info === "string") {
tooltip = ( tooltip = (
<> <>
<span dangerouslySetInnerHTML={{ __html: aug.info }} /> <span>{aug.info}</span>
<br /> <br />
<br /> <br />
{aug.stats} {aug.stats}
</> </>
); );
else } else
tooltip = ( tooltip = (
<> <>
{aug.info} {aug.info}
@ -154,25 +158,60 @@ export function PurchaseableAugmentation(props: IProps): React.ReactElement {
</> </>
); );
function handleClick(): void {
if (color === "error") return;
if (!Settings.SuppressBuyAugmentationConfirmation) {
const popupId = "purchase-augmentation-popup";
createPopup(popupId, PurchaseAugmentationPopup, {
aug: aug,
faction: props.faction,
player: props.p,
rerender: props.rerender,
popupId: popupId,
});
} else {
purchaseAugmentation(aug, props.faction);
props.rerender();
}
}
return ( return (
<li key={aug.name}> <TableRow>
<span {!props.owned && (
style={{ <TableCell key={0}>
margin: "4px", <Button onClick={handleClick} color={color}>
padding: "4px", Buy
}} </Button>
> </TableCell>
<StdButton )}
disabled={disabled} <TableCell key={1}>
onClick={handleClick} <Box display="flex">
style={{ <Tooltip
display: "inline-block", title={<Typography>{tooltip}</Typography>}
}} placement="top"
text={btnTxt} disableFocusListener
tooltip={tooltip} disableTouchListener
enterNextDelay={1000}
enterDelay={500}
leaveDelay={0}
leaveTouchDelay={0}
>
<Typography>{btnTxt}</Typography>
</Tooltip>
</Box>
</TableCell>
{!props.owned && (
<Requirements
key={2}
augName={props.augName}
p={props.p}
cost={moneyCost}
rep={repCost}
hasReq={hasReq}
hasRep={hasRep}
hasCost={hasCost}
/> />
<p style={txtStyle}>{status}</p> )}
</span> </TableRow>
</li>
); );
} }

@ -218,28 +218,41 @@ export class GangMember {
return points.hack > 0 || points.str > 0 || points.def > 0 || points.dex > 0 || points.agi > 0 || points.cha > 0; return points.hack > 0 || points.str > 0 || points.def > 0 || points.dex > 0 || points.agi > 0 || points.cha > 0;
} }
getAscensionResults(): IMults { getCurrentAscensionMults(): IMults {
return {
hack: this.calculateAscensionMult(this.hack_asc_points),
str: this.calculateAscensionMult(this.str_asc_points),
def: this.calculateAscensionMult(this.def_asc_points),
dex: this.calculateAscensionMult(this.dex_asc_points),
agi: this.calculateAscensionMult(this.agi_asc_points),
cha: this.calculateAscensionMult(this.cha_asc_points)
}
}
getAscensionMultsAfterAscend(): IMults {
const points = this.getGainedAscensionPoints(); const points = this.getGainedAscensionPoints();
return { return {
hack: hack: this.calculateAscensionMult(this.hack_asc_points + points.hack),
this.calculateAscensionMult(this.hack_asc_points + points.hack) / str: this.calculateAscensionMult(this.str_asc_points + points.str),
this.calculateAscensionMult(this.hack_asc_points), def: this.calculateAscensionMult(this.def_asc_points + points.def),
str: dex: this.calculateAscensionMult(this.dex_asc_points + points.dex),
this.calculateAscensionMult(this.str_asc_points + points.str) / agi: this.calculateAscensionMult(this.agi_asc_points + points.agi),
this.calculateAscensionMult(this.str_asc_points), cha: this.calculateAscensionMult(this.cha_asc_points + points.cha),
def: }
this.calculateAscensionMult(this.def_asc_points + points.def) / }
this.calculateAscensionMult(this.def_asc_points),
dex: getAscensionResults(): IMults {
this.calculateAscensionMult(this.dex_asc_points + points.dex) / const postAscend = this.getAscensionMultsAfterAscend();
this.calculateAscensionMult(this.dex_asc_points), const preAscend = this.getCurrentAscensionMults();
agi:
this.calculateAscensionMult(this.agi_asc_points + points.agi) / return {
this.calculateAscensionMult(this.agi_asc_points), hack: postAscend.hack / preAscend.hack,
cha: str: postAscend.str / preAscend.str,
this.calculateAscensionMult(this.cha_asc_points + points.cha) / def: postAscend.def / preAscend.def,
this.calculateAscensionMult(this.cha_asc_points), dex: postAscend.dex / preAscend.dex,
}; agi: postAscend.agi / preAscend.agi,
cha: postAscend.cha / preAscend.cha,
}
} }
ascend(): IAscensionResult { ascend(): IAscensionResult {

@ -57,7 +57,9 @@ export function AscensionPopup(props: IProps): React.ReactElement {
removePopup(props.popupId); removePopup(props.popupId);
} }
const ascendBenefits = props.member.getAscensionResults(); // const ascendBenefits = props.member.getAscensionResults();
const preAscend = props.member.getCurrentAscensionMults();
const postAscend = props.member.getAscensionMultsAfterAscend();
return ( return (
<> <>
@ -72,17 +74,17 @@ export function AscensionPopup(props: IProps): React.ReactElement {
<br /> <br />
In return, they will gain the following permanent boost to stat multipliers: In return, they will gain the following permanent boost to stat multipliers:
<br /> <br />
Hacking: x{numeralWrapper.format(ascendBenefits.hack, "0.000")} Hacking: x{numeralWrapper.format(preAscend.hack, "0.000")} =&gt; x{numeralWrapper.format(postAscend.hack, "0.000")}
<br /> <br />
Strength: x{numeralWrapper.format(ascendBenefits.str, "0.000")} Strength: x{numeralWrapper.format(preAscend.str, "0.000")} =&gt; x{numeralWrapper.format(postAscend.str, "0.000")}
<br /> <br />
Defense: x{numeralWrapper.format(ascendBenefits.def, "0.000")} Defense: x{numeralWrapper.format(preAscend.def, "0.000")} =&gt; x{numeralWrapper.format(postAscend.def, "0.000")}
<br /> <br />
Dexterity: x{numeralWrapper.format(ascendBenefits.dex, "0.000")} Dexterity: x{numeralWrapper.format(preAscend.dex, "0.000")} =&gt; x{numeralWrapper.format(postAscend.dex, "0.000")}
<br /> <br />
Agility: x{numeralWrapper.format(ascendBenefits.agi, "0.000")} Agility: x{numeralWrapper.format(preAscend.agi, "0.000")} =&gt; x{numeralWrapper.format(postAscend.agi, "0.000")}
<br /> <br />
Charisma: x{numeralWrapper.format(ascendBenefits.cha, "0.000")} Charisma: x{numeralWrapper.format(preAscend.cha, "0.000")} =&gt; x{numeralWrapper.format(postAscend.cha, "0.000")}
<br /> <br />
</pre> </pre>
<button className="std-button" onClick={confirm}> <button className="std-button" onClick={confirm}>

@ -41,6 +41,7 @@ export function SpecialLocation(props: IProps): React.ReactElement {
createPopup(popupId, CreateCorporationPopup, { createPopup(popupId, CreateCorporationPopup, {
player: player, player: player,
popupId: popupId, popupId: popupId,
router: router,
}); });
} }

@ -2,7 +2,7 @@ import { makeRuntimeRejectMsg } from "./NetscriptEvaluator";
import { ScriptUrl } from "./Script/ScriptUrl"; import { ScriptUrl } from "./Script/ScriptUrl";
// Makes a blob that contains the code of a given script. // Makes a blob that contains the code of a given script.
export function makeScriptBlob(code) { function makeScriptBlob(code) {
return new Blob([code], { type: "text/javascript" }); return new Blob([code], { type: "text/javascript" });
} }
@ -93,7 +93,7 @@ function shouldCompile(script, scripts) {
* the script parameter. * the script parameter.
*/ */
// BUG: apparently seen is never consulted. Oops. // BUG: apparently seen is never consulted. Oops.
export function _getScriptUrls(script, scripts, seen) { function _getScriptUrls(script, scripts, seen) {
// Inspired by: https://stackoverflow.com/a/43834063/91401 // Inspired by: https://stackoverflow.com/a/43834063/91401
/** @type {ScriptUrl[]} */ /** @type {ScriptUrl[]} */
const urlStack = []; const urlStack = [];

@ -70,16 +70,20 @@ export function CovenantPurchasesRoot(props: IProps): React.ReactElement {
return ( return (
<div> <div>
<PopupCloseButton popup={PopupId} text={"Close"} /> <PopupCloseButton popup={PopupId} text={"Close"} />
<p> {props.p.sleevesFromCovenant < MaxSleevesFromCovenant && (
Would you like to purchase an additional Duplicate Sleeve from The Covenant for{" "} <>
<Money money={purchaseCost()} player={props.p} />? <p>
</p> Would you like to purchase an additional Duplicate Sleeve from The Covenant for{" "}
<br /> <Money money={purchaseCost()} player={props.p} />?
<p> </p>
These Duplicate Sleeves are permanent (they persist through BitNodes). You can purchase a total of{" "} <br />
{MaxSleevesFromCovenant} from The Covenant. <p>
</p> These Duplicate Sleeves are permanent (they persist through BitNodes). You can purchase a total of{" "}
<StdButton disabled={purchaseDisabled} onClick={purchaseOnClick} text={"Purchase"} /> {MaxSleevesFromCovenant} from The Covenant.
</p>
<StdButton disabled={purchaseDisabled} onClick={purchaseOnClick} text={"Purchase"} />
</>
)}
<br /> <br />
<br /> <br />
<p> <p>

@ -6,6 +6,7 @@ import { removePopup } from "../../../ui/React/createPopup";
import { Money } from "../../../ui/React/Money"; import { Money } from "../../../ui/React/Money";
import { WorldMap } from "../../../ui/React/WorldMap"; import { WorldMap } from "../../../ui/React/WorldMap";
import { CityName } from "../../../Locations/data/CityNames"; import { CityName } from "../../../Locations/data/CityNames";
import { Settings } from "../../../Settings/Settings";
import { dialogBoxCreate } from "../../../../utils/DialogBox"; import { dialogBoxCreate } from "../../../../utils/DialogBox";
interface IProps { interface IProps {
@ -34,7 +35,15 @@ export function TravelPopup(props: IProps): React.ReactElement {
study. Traveling to a different city costs <Money money={CONSTANTS.TravelCost} player={props.player} />. It will study. Traveling to a different city costs <Money money={CONSTANTS.TravelCost} player={props.player} />. It will
also set your current sleeve task to idle. also set your current sleeve task to idle.
</p> </p>
<WorldMap currentCity={props.sleeve.city} onTravel={(city: CityName) => travel(city)} /> {Settings.DisableASCIIArt ? (
Object.values(CityName).map((city: CityName) => (
<button key={city} className="std-button" onClick={() => travel(city)}>
{city}
</button>
))
) : (
<WorldMap currentCity={props.sleeve.city} onTravel={(city: CityName) => travel(city)} />
)}
</> </>
); );
} }

@ -10,10 +10,6 @@ import { SourceFiles } from "./SourceFile/SourceFiles";
import { dialogBoxCreate } from "../utils/DialogBox"; import { dialogBoxCreate } from "../utils/DialogBox";
let redPillFlag = false; let redPillFlag = false;
function hackWorldDaemon(router, flume = false, quick = false) {
router.toBitVerse(flume, quick);
redPillFlag = true;
}
function giveSourceFile(bitNodeNumber) { function giveSourceFile(bitNodeNumber) {
var sourceFileKey = "SourceFile" + bitNodeNumber.toString(); var sourceFileKey = "SourceFile" + bitNodeNumber.toString();
@ -86,4 +82,4 @@ export function enterBitNode(router, flume, destroyedBitNode, newBitNode) {
prestigeSourceFile(flume); prestigeSourceFile(flume);
} }
export { redPillFlag, hackWorldDaemon }; export { redPillFlag };

8
src/SaveObject.d.ts vendored

@ -1,2 +1,6 @@
export declare const saveObject: any; export declare const saveObject: {
export declare function openImportFileHandler(evt: any): void; getSaveString: () => string;
saveGame: () => void;
exportGame: () => void;
};
export declare function loadGame(s: string): boolean;

@ -22,6 +22,7 @@ import * as ExportBonus from "./ExportBonus";
import { dialogBoxCreate } from "../utils/DialogBox"; import { dialogBoxCreate } from "../utils/DialogBox";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners"; import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../utils/JSONReviver"; import { Reviver, Generic_toJSON, Generic_fromJSON } from "../utils/JSONReviver";
import { save } from "./db";
import Decimal from "decimal.js"; import Decimal from "decimal.js";
@ -84,33 +85,12 @@ BitburnerSaveObject.prototype.getSaveString = function () {
return saveString; return saveString;
}; };
BitburnerSaveObject.prototype.saveGame = function (db) { BitburnerSaveObject.prototype.saveGame = function () {
var saveString = this.getSaveString(); const saveString = this.getSaveString();
// We'll save to both localstorage and indexedDb save(saveString)
var objectStore = db.transaction(["savestring"], "readwrite").objectStore("savestring"); .then(() => createStatusText("Game saved!"))
var request = objectStore.put(saveString, "save"); .catch((err) => console.error(err));
request.onerror = function (e) {
console.error("Error saving game to IndexedDB: " + e);
};
try {
window.localStorage.setItem("bitburnerSave", saveString);
} catch (e) {
if (e.code == 22) {
createStatusText("Save failed for localStorage! Check console(F12)");
console.error(
"Failed to save game to localStorage because the size of the save file " +
"is too large. However, the game will still be saved to IndexedDb if your browser " +
"supports it. If you would like to save to localStorage as well, then " +
"consider killing several of your scripts to " +
"fix this, or increasing the size of your browsers localStorage",
);
}
}
createStatusText("Game saved!");
}; };
// Makes necessary changes to the loaded/imported data to ensure // Makes necessary changes to the loaded/imported data to ensure
@ -155,16 +135,10 @@ function evaluateVersionCompatibility(ver) {
} }
function loadGame(saveString) { function loadGame(saveString) {
if (saveString === "" || saveString == null || saveString === undefined) { if (!saveString) return false;
if (!window.localStorage.getItem("bitburnerSave")) { saveString = decodeURIComponent(escape(atob(saveString)));
return false;
}
saveString = decodeURIComponent(escape(atob(window.localStorage.getItem("bitburnerSave"))));
} else {
saveString = decodeURIComponent(escape(atob(saveString)));
}
var saveObj = JSON.parse(saveString, Reviver); const saveObj = JSON.parse(saveString, Reviver);
loadPlayer(saveObj.PlayerSave); loadPlayer(saveObj.PlayerSave);
loadAllServers(saveObj.AllServersSave); loadAllServers(saveObj.AllServersSave);
@ -224,19 +198,12 @@ function loadGame(saveString) {
} else { } else {
Settings.init(); Settings.init();
} }
if (saveObj.hasOwnProperty("FconfSettingsSave")) {
try {
loadFconf(saveObj.FconfSettingsSave);
} catch (e) {
console.error("ERROR: Failed to parse .fconf Settings.");
}
}
if (saveObj.hasOwnProperty("LastExportBonus")) { if (saveObj.hasOwnProperty("LastExportBonus")) {
try { try {
ExportBonus.setLastExportBonus(JSON.parse(saveObj.LastExportBonus)); ExportBonus.setLastExportBonus(JSON.parse(saveObj.LastExportBonus));
} catch (err) { } catch (err) {
ExportBonus.setLastExportBonus(new Date().getTime()); ExportBonus.setLastExportBonus(new Date().getTime());
console.error("ERROR: Failed to parse .fconf Settings " + err); console.error("ERROR: Failed to parse last export bonus Settings " + err);
} }
} }
if (saveObj.hasOwnProperty("VersionSave")) { if (saveObj.hasOwnProperty("VersionSave")) {
@ -267,173 +234,6 @@ function loadGame(saveString) {
return true; return true;
} }
function loadImportedGame(saveObj, saveString) {
var tempSaveObj = null;
var tempPlayer = null;
// Check to see if the imported save file can be parsed. If any
// errors are caught it will fail
try {
var decodedSaveString = decodeURIComponent(escape(atob(saveString)));
tempSaveObj = JSON.parse(decodedSaveString, Reviver);
tempPlayer = JSON.parse(tempSaveObj.PlayerSave, Reviver);
// Parse Decimal.js objects
tempPlayer.money = new Decimal(tempPlayer.money);
JSON.parse(tempSaveObj.AllServersSave, Reviver);
JSON.parse(tempSaveObj.CompaniesSave, Reviver);
JSON.parse(tempSaveObj.FactionsSave, Reviver);
JSON.parse(tempSaveObj.SpecialServerIpsSave, Reviver);
if (tempSaveObj.hasOwnProperty("AliasesSave")) {
try {
JSON.parse(tempSaveObj.AliasesSave, Reviver);
} catch (e) {
console.error(`Parsing Aliases save failed: ${e}`);
}
}
if (tempSaveObj.hasOwnProperty("GlobalAliases")) {
try {
JSON.parse(tempSaveObj.AliasesSave, Reviver);
} catch (e) {
console.error(`Parsing Global Aliases save failed: ${e}`);
}
}
if (tempSaveObj.hasOwnProperty("MessagesSave")) {
try {
JSON.parse(tempSaveObj.MessagesSave, Reviver);
} catch (e) {
console.error(`Parsing Messages save failed: ${e}`);
initMessages();
}
} else {
initMessages();
}
if (saveObj.hasOwnProperty("StockMarketSave")) {
try {
JSON.parse(tempSaveObj.StockMarketSave, Reviver);
} catch (e) {
console.error(`Parsing StockMarket save failed: ${e}`);
}
}
if (saveObj.hasOwnProperty("LastExportBonus")) {
try {
ExportBonus.setLastExportBonus(JSON.parse(saveObj.LastExportBonus));
} catch (err) {
ExportBonus.setLastExportBonus(new Date().getTime());
console.error("ERROR: Failed to parse .fconf Settings " + err);
}
}
if (tempSaveObj.hasOwnProperty("VersionSave")) {
try {
var ver = JSON.parse(tempSaveObj.VersionSave, Reviver);
evaluateVersionCompatibility(ver);
} catch (e) {
console.error("Parsing Version save failed: " + e);
}
}
if (tempPlayer.inGang() && tempSaveObj.hasOwnProperty("AllGangsSave")) {
try {
loadAllGangs(tempSaveObj.AllGangsSave);
} catch (e) {
console.error(`Failed to parse AllGangsSave: {e}`);
throw e;
}
}
} catch (e) {
dialogBoxCreate("Error importing game: " + e.toString());
return false;
}
// Since the save file is valid, load everything for real
saveString = decodeURIComponent(escape(atob(saveString)));
saveObj = JSON.parse(saveString, Reviver);
loadPlayer(saveObj.PlayerSave);
loadAllServers(saveObj.AllServersSave);
loadCompanies(saveObj.CompaniesSave);
loadFactions(saveObj.FactionsSave);
loadSpecialServerIps(saveObj.SpecialServerIpsSave);
if (saveObj.hasOwnProperty("AliasesSave")) {
try {
loadAliases(saveObj.AliasesSave);
} catch (e) {
loadAliases("");
}
} else {
loadAliases("");
}
if (saveObj.hasOwnProperty("GlobalAliasesSave")) {
try {
loadGlobalAliases(saveObj.GlobalAliasesSave);
} catch (e) {
loadGlobalAliases("");
}
} else {
loadGlobalAliases("");
}
if (saveObj.hasOwnProperty("MessagesSave")) {
try {
loadMessages(saveObj.MessagesSave);
} catch (e) {
initMessages();
}
} else {
initMessages();
}
if (saveObj.hasOwnProperty("StockMarketSave")) {
try {
loadStockMarket(saveObj.StockMarketSave);
} catch (e) {
loadStockMarket("");
}
} else {
loadStockMarket("");
}
if (saveObj.hasOwnProperty("SettingsSave")) {
try {
Settings.load(saveObj.SettingsSave);
} catch (e) {
Settings.init();
}
} else {
Settings.init();
}
if (saveObj.hasOwnProperty("FconfSettingsSave")) {
try {
loadFconf(saveObj.FconfSettingsSave);
} catch (e) {
console.error("ERROR: Failed to load .fconf settings when importing");
}
}
if (saveObj.hasOwnProperty("VersionSave")) {
try {
var ver = JSON.parse(saveObj.VersionSave, Reviver);
evaluateVersionCompatibility(ver);
if (ver != CONSTANTS.Version) {
createNewUpdateText();
}
} catch (e) {
createNewUpdateText();
}
} else {
createNewUpdateText();
}
if (Player.inGang() && saveObj.hasOwnProperty("AllGangsSave")) {
try {
loadAllGangs(saveObj.AllGangsSave);
} catch (e) {
console.error("ERROR: Failed to parse AllGangsSave: " + e);
}
}
saveObject.saveGame(Engine.indexedDb);
location.reload();
return true;
}
BitburnerSaveObject.prototype.exportGame = function () { BitburnerSaveObject.prototype.exportGame = function () {
const saveString = this.getSaveString(); const saveString = this.getSaveString();
@ -460,31 +260,6 @@ BitburnerSaveObject.prototype.exportGame = function () {
} }
}; };
BitburnerSaveObject.prototype.importGame = function () {
if (window.File && window.FileReader && window.FileList && window.Blob) {
var fileSelector = clearEventListeners("import-game-file-selector");
fileSelector.addEventListener("change", openImportFileHandler, false);
$("#import-game-file-selector").click();
} else {
dialogBoxCreate("ERR: Your browser does not support HTML5 File API. Cannot import.");
}
};
BitburnerSaveObject.prototype.deleteGame = function (db) {
// Delete from local storage
if (window.localStorage.getItem("bitburnerSave")) {
window.localStorage.removeItem("bitburnerSave");
}
// Delete from indexedDB
var request = db.transaction(["savestring"], "readwrite").objectStore("savestring").delete("save");
request.onsuccess = function () {};
request.onerror = function (e) {
console.error(`Failed to delete save from indexedDb: ${e}`);
};
createStatusText("Game deleted!");
};
function createNewUpdateText() { function createNewUpdateText() {
dialogBoxCreate( dialogBoxCreate(
"New update!<br>" + "New update!<br>" +
@ -514,19 +289,4 @@ BitburnerSaveObject.fromJSON = function (value) {
Reviver.constructors.BitburnerSaveObject = BitburnerSaveObject; Reviver.constructors.BitburnerSaveObject = BitburnerSaveObject;
function openImportFileHandler(evt) { export { saveObject, loadGame };
var file = evt.target.files[0];
if (!file) {
dialogBoxCreate("Invalid file selected");
return;
}
var reader = new FileReader();
reader.onload = function (e) {
var contents = e.target.result;
loadImportedGame(saveObject, contents);
};
reader.readAsText(file);
}
export { saveObject, loadGame, openImportFileHandler };

@ -3,7 +3,7 @@ export type Position = {
column: number; column: number;
}; };
export class PositionTracker { class PositionTracker {
positions: Map<string, Position>; positions: Map<string, Position>;
constructor() { constructor() {

@ -5,6 +5,14 @@ import { OwnedAugmentationsOrderSetting, PurchaseAugmentationsOrderSetting } fro
* Represents the default settings the player could customize. * Represents the default settings the player could customize.
*/ */
interface IDefaultSettings { interface IDefaultSettings {
/**
* How many servers per page
*/
ActiveScriptsServerPageSize: number;
/**
* How many scripts per page
*/
ActiveScriptsScriptPageSize: number;
/** /**
* How often the game should autosave the player's progress, in seconds. * How often the game should autosave the player's progress, in seconds.
*/ */
@ -101,6 +109,8 @@ interface ISettings extends IDefaultSettings {
} }
const defaultSettings: IDefaultSettings = { const defaultSettings: IDefaultSettings = {
ActiveScriptsServerPageSize: 10,
ActiveScriptsScriptPageSize: 10,
AutosaveInterval: 60, AutosaveInterval: 60,
CodeInstructionRunTime: 50, CodeInstructionRunTime: 50,
DisableASCIIArt: false, DisableASCIIArt: false,
@ -123,6 +133,8 @@ const defaultSettings: IDefaultSettings = {
*/ */
// tslint:disable-next-line:variable-name // tslint:disable-next-line:variable-name
export const Settings: ISettings & ISelfInitializer & ISelfLoading = { export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
ActiveScriptsServerPageSize: defaultSettings.ActiveScriptsServerPageSize,
ActiveScriptsScriptPageSize: defaultSettings.ActiveScriptsScriptPageSize,
AutosaveInterval: defaultSettings.AutosaveInterval, AutosaveInterval: defaultSettings.AutosaveInterval,
CodeInstructionRunTime: 25, CodeInstructionRunTime: 25,
DisableASCIIArt: defaultSettings.DisableASCIIArt, DisableASCIIArt: defaultSettings.DisableASCIIArt,

@ -51,7 +51,6 @@ import { Settings } from "../../Settings/Settings";
import { redPillFlag } from "../../RedPill"; import { redPillFlag } from "../../RedPill";
import { inMission } from "../../Missions"; import { inMission } from "../../Missions";
import { cinematicTextFlag } from "../../CinematicText";
import { KEY } from "../../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
import { FconfSettings } from "../../Fconf/FconfSettings"; import { FconfSettings } from "../../Fconf/FconfSettings";
@ -268,7 +267,7 @@ export function SidebarRoot(props: IProps): React.ReactElement {
// Alt-o - Options // Alt-o - Options
function handleShortcuts(this: Document, event: KeyboardEvent): any { function handleShortcuts(this: Document, event: KeyboardEvent): any {
if (Settings.DisableHotkeys) return; if (Settings.DisableHotkeys) return;
if (props.player.isWorking || redPillFlag || inMission || cinematicTextFlag) return; if (props.player.isWorking || redPillFlag || inMission) return;
if (event.keyCode == KEY.T && event.altKey) { if (event.keyCode == KEY.T && event.altKey) {
event.preventDefault(); event.preventDefault();
clickTerminal(); clickTerminal();

@ -4,43 +4,42 @@ import { IMap } from "../types";
export const TerminalHelpText: string[] = [ export const TerminalHelpText: string[] = [
"Type 'help name' to learn more about the command ", "Type 'help name' to learn more about the command ",
"", "",
'alias [-g] [name="value"] Create or display Terminal aliases', 'alias [-g] [name="value"] Create or display Terminal aliases',
"analyze Get information about the current machine ", "analyze Get information about the current machine ",
"backdoor Install a backdoor on the current machine ", "backdoor Install a backdoor on the current machine ",
"buy [-l/program] Purchase a program through the Dark Web", "buy [-l/program] Purchase a program through the Dark Web",
"cat [file] Display a .msg, .lit, or .txt file", "cat [file] Display a .msg, .lit, or .txt file",
"cd [dir] Change to a new directory", "cd [dir] Change to a new directory",
"check [script] [args...] Print a script's logs to Terminal", "check [script] [args...] Print a script's logs to Terminal",
"clear Clear all text on the terminal ", "clear Clear all text on the terminal ",
"cls See 'clear' command ", "cls See 'clear' command ",
"connect [ip/hostname] Connects to a remote server", "connect [ip/hostname] Connects to a remote server",
"download [script/text file] Downloads scripts or text files to your computer", "download [script/text file] Downloads scripts or text files to your computer",
"expr [math expression] Evaluate a mathematical expression", "expr [math expression] Evaluate a mathematical expression",
"free Check the machine's memory (RAM) usage", "free Check the machine's memory (RAM) usage",
"hack Hack the current machine", "hack Hack the current machine",
"help [command] Display this help text, or the help text for a command", "help [command] Display this help text, or the help text for a command",
"home Connect to home computer", "home Connect to home computer",
"hostname Displays the hostname of the machine", "hostname Displays the hostname of the machine",
"ifconfig Displays the IP address of the machine", "ifconfig Displays the IP address of the machine",
"kill [script/pid] [args...] Stops the specified script on the current server ", "kill [script/pid] [args...] Stops the specified script on the current server ",
"killall Stops all running scripts on the current machine", "killall Stops all running scripts on the current machine",
"ls [dir] [| grep pattern] Displays all files on the machine", "ls [dir] [| grep pattern] Displays all files on the machine",
"lscpu Displays the number of CPU cores 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", "mv [src] [dest] Move/rename a text or script file",
"nano [file] Text editor - Open up and edit a script or text file", "nano [file] Text editor - Open up and edit a script or text file",
"ps Display all scripts that are currently running", "ps Display all scripts that are currently running",
"rm [file] Delete a file from the server", "rm [file] Delete a file from the server",
"run [name] [-t] [n] [args...] Execute a program or script", "run [name] [-t n] [--tail] [args...] Execute a program or script",
"scan Prints all immediately-available network connections", "scan Prints all immediately-available network connections",
"scan-analyze [d] [-a] Prints info for all servers up to <i>d</i> nodes away", "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", "sudov Shows whether you have root access on this computer",
"tail [script] [args...] Displays dynamic logs for the specified script", "tail [script] [args...] Displays dynamic logs for the specified script",
"theme [preset] | bg txt hlgt Change the color scheme of the UI", "top Displays all running scripts and their RAM usage",
"top Displays all running scripts and their RAM usage", "unalias [alias name] Deletes the specified alias",
"unalias [alias name] Deletes the specified alias", "wget [url] [target file] Retrieves code/text from a web server",
"wget [url] [target file] Retrieves code/text from a web server",
]; ];
export const HelpTexts: IMap<string[]> = { export const HelpTexts: IMap<string[]> = {
@ -268,7 +267,7 @@ export const HelpTexts: IMap<string[]> = {
lscpu: ["lscpu", " ", "Prints the number of CPU Cores the current server has"], lscpu: ["lscpu", " ", "Prints the number of CPU Cores the current server has"],
mem: [ mem: [
"mem [script name] [-t] [num threads]", "mem [script name] [-t num_threads]",
" ", " ",
"Displays the amount of RAM needed to run the specified script with a single thread. The command can also be used to print ", "Displays the amount of RAM needed to run the specified script with a single thread. The command can also be used to print ",
"the amount of RAM needed to run a script with multiple threads using the '-t' flag. If the '-t' flag is specified, then ", "the amount of RAM needed to run a script with multiple threads using the '-t' flag. If the '-t' flag is specified, then ",
@ -374,24 +373,6 @@ export const HelpTexts: IMap<string[]> = {
" ", " ",
"tail foo.script 10 50000", "tail foo.script 10 50000",
], ],
theme: [
"theme [preset] | [#background #text #highlight]",
" ",
"Change the color of the game's user interface",
" ",
"This command can be called with a preset theme. Currently, the supported presets are 'default', 'muted', and 'solarized'. ",
"However, you can also specify your own color scheme using hex values. To do so, you must specify three hex color values ",
"for the background color, the text color, and the highlight color. These hex values must be preceded by a pound sign (#) and ",
"must be either 3 or 6 digits. Example:",
" ",
"theme #ffffff #385 #235012",
" ",
"A color picker such as ",
"<a href='https://www.google.com/search?q=color+picker&oq=color+picker&aqs=chrome.0.0l6.951j0j1&sourceid=chrome&ie=UTF-8' target='_blank'>Google's</a> ",
"can be used to get your desired hex color values",
" ",
"Themes are not saved, so when the game is closed and then re-opened or reloaded then it will revert back to the default theme.",
],
top: [ top: [
"top", "top",
" ", " ",

@ -61,7 +61,6 @@ import { scananalyze } from "./commands/scananalyze";
import { scp } from "./commands/scp"; import { scp } from "./commands/scp";
import { sudov } from "./commands/sudov"; import { sudov } from "./commands/sudov";
import { tail } from "./commands/tail"; import { tail } from "./commands/tail";
import { theme } from "./commands/theme";
import { top } from "./commands/top"; import { top } from "./commands/top";
import { unalias } from "./commands/unalias"; import { unalias } from "./commands/unalias";
import { wget } from "./commands/wget"; import { wget } from "./commands/wget";
@ -345,14 +344,10 @@ export class Terminal implements ITerminal {
case CodingContractResult.Failure: case CodingContractResult.Failure:
++contract.tries; ++contract.tries;
if (contract.tries >= contract.getMaxNumTries()) { if (contract.tries >= contract.getMaxNumTries()) {
this.print("Contract <p style='color:red;display:inline'>FAILED</p> - Contract is now self-destructing"); this.print("Contract FAILED - Contract is now self-destructing");
serv.removeContract(contract); serv.removeContract(contract);
} else { } else {
this.print( this.print(`Contract FAILED - ${contract.getMaxNumTries() - contract.tries} tries remaining`);
`Contract <p style='color:red;display:inline'>FAILED</p> - ${
contract.getMaxNumTries() - contract.tries
} tries remaining`,
);
} }
break; break;
case CodingContractResult.Cancelled: case CodingContractResult.Cancelled:
@ -683,7 +678,6 @@ export class Terminal implements ITerminal {
scp: scp, scp: scp,
sudov: sudov, sudov: sudov,
tail: tail, tail: tail,
theme: theme,
top: top, top: top,
unalias: unalias, unalias: unalias,
wget: wget, wget: wget,

@ -1,60 +0,0 @@
import { ITerminal } from "../ITerminal";
import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { FconfSettings } from "../../Fconf/FconfSettings";
export function theme(
terminal: ITerminal,
router: IRouter,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 1 && args.length !== 3) {
terminal.error("Incorrect number of arguments.");
terminal.error(
"Usage: theme [default|muted|solarized] | #[background color hex] #[text color hex] #[highlight color hex]",
);
} else if (args.length === 1) {
const themeName = args[0];
if (themeName == "default") {
document.body.style.setProperty("--my-highlight-color", "#ffffff");
document.body.style.setProperty("--my-font-color", "#66ff33");
document.body.style.setProperty("--my-background-color", "#000000");
document.body.style.setProperty("--my-prompt-color", "#f92672");
} else if (themeName == "muted") {
document.body.style.setProperty("--my-highlight-color", "#ffffff");
document.body.style.setProperty("--my-font-color", "#66ff33");
document.body.style.setProperty("--my-background-color", "#252527");
} else if (themeName == "solarized") {
document.body.style.setProperty("--my-highlight-color", "#6c71c4");
document.body.style.setProperty("--my-font-color", "#839496");
document.body.style.setProperty("--my-background-color", "#002b36");
} else {
return terminal.error("Theme not found");
}
FconfSettings.THEME_HIGHLIGHT_COLOR = document.body.style.getPropertyValue("--my-highlight-color");
FconfSettings.THEME_FONT_COLOR = document.body.style.getPropertyValue("--my-font-color");
FconfSettings.THEME_BACKGROUND_COLOR = document.body.style.getPropertyValue("--my-background-color");
FconfSettings.THEME_PROMPT_COLOR = document.body.style.getPropertyValue("--my-prompt-color");
} else {
const inputBackgroundHex = args[0] + "";
const inputTextHex = args[1] + "";
const inputHighlightHex = args[2] + "";
if (
/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(inputBackgroundHex) &&
/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(inputTextHex) &&
/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(inputHighlightHex)
) {
document.body.style.setProperty("--my-highlight-color", inputHighlightHex);
document.body.style.setProperty("--my-font-color", inputTextHex);
document.body.style.setProperty("--my-background-color", inputBackgroundHex);
FconfSettings.THEME_HIGHLIGHT_COLOR = document.body.style.getPropertyValue("--my-highlight-color");
FconfSettings.THEME_FONT_COLOR = document.body.style.getPropertyValue("--my-font-color");
FconfSettings.THEME_BACKGROUND_COLOR = document.body.style.getPropertyValue("--my-background-color");
} else {
return terminal.error("Invalid Hex Input for theme");
}
}
}

86
src/db.tsx Normal file

@ -0,0 +1,86 @@
import { Engine } from "./engine";
import { createStatusText } from "./ui/createStatusText";
function getDB(): Promise<IDBObjectStore> {
return new Promise((resolve, reject) => {
if (!window.indexedDB) {
reject("Indexed DB does not exists");
}
/**
* DB is called bitburnerSave
* Object store is called savestring
* key for the Object store is called save
* Version `1` is important
*/
const indexedDbRequest: IDBOpenDBRequest = window.indexedDB.open("bitburnerSave", 1);
// This is called when there's no db to begin with. It's important, don't remove it.
indexedDbRequest.onupgradeneeded = function (this: IDBRequest<IDBDatabase>) {
const db = this.result;
db.createObjectStore("savestring");
};
indexedDbRequest.onerror = function (this: IDBRequest<IDBDatabase>, ev: Event) {
reject(`Failed to get IDB ${ev}`);
};
indexedDbRequest.onsuccess = function (this: IDBRequest<IDBDatabase>, ev: Event) {
const db = this.result;
if (!db) {
reject("database loadign result was undefined");
return;
}
resolve(db.transaction(["savestring"], "readwrite").objectStore("savestring"));
};
});
}
interface ILoadCallback {
success: (s: string) => void;
error?: () => void;
}
export function load(): Promise<string> {
return new Promise(async (resolve, reject) => {
await getDB()
.then((db) => {
return new Promise<string>((resolve, reject) => {
const request: IDBRequest<string> = db.get("save");
request.onerror = function (this: IDBRequest<string>, ev: Event) {
reject("Error in Database request to get savestring: " + ev);
};
request.onsuccess = function (this: IDBRequest<string>) {
resolve(this.result);
};
}).then((saveString) => resolve(saveString));
})
.catch((r) => reject(r));
});
}
interface ISaveCallback {
success: () => void;
error?: () => void;
}
export function save(saveString: string): Promise<void> {
return getDB().then((db) => {
return new Promise<void>((resolve, reject) => {
// We'll save to both localstorage and indexedDb
const request = db.put(saveString, "save");
request.onerror = function (e) {
reject("Error saving game to IndexedDB: " + e);
};
request.onsuccess = () => resolve();
});
});
}
export function deleteGame(): Promise<void> {
return getDB().then((db) => {
db.delete("save");
});
}

2
src/engine.d.ts vendored

@ -1,3 +1 @@
export declare function load(cb: () => void): void;
export declare const Engine: IEngine; export declare const Engine: IEngine;

@ -44,14 +44,12 @@ import { Reputation } from "./ui/React/Reputation";
import { dialogBoxCreate } from "../utils/DialogBox"; import { dialogBoxCreate } from "../utils/DialogBox";
import { exceptionAlert } from "../utils/helpers/exceptionAlert"; import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import "./Exploits/tampering"; import { startTampering } from "./Exploits/tampering";
import "./Exploits/unclickable"; import { startUnclickable } from "./Exploits/unclickable";
import React from "react"; import React from "react";
const Engine = { const Engine = {
indexedDb: undefined,
// Time variables (milliseconds unix epoch time) // Time variables (milliseconds unix epoch time)
_lastUpdate: new Date().getTime(), _lastUpdate: new Date().getTime(),
@ -196,7 +194,7 @@ const Engine = {
Engine.Counters.autoSaveCounter = Infinity; Engine.Counters.autoSaveCounter = Infinity;
} else { } else {
Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5; Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5;
saveObject.saveGame(Engine.indexedDb); saveObject.saveGame();
} }
} }
@ -248,6 +246,8 @@ const Engine = {
}, },
load: function (saveString) { load: function (saveString) {
startTampering();
startUnclickable();
// Load game from save or create new game // Load game from save or create new game
if (loadGame(saveString)) { if (loadGame(saveString)) {
initBitNodeMultipliers(Player); initBitNodeMultipliers(Player);
@ -420,48 +420,4 @@ const Engine = {
}, },
}; };
function load(cb) { export { Engine };
if (!window.indexedDB) {
return Engine.load(null); // Will try to load from localstorage
}
/**
* DB is called bitburnerSave
* Object store is called savestring
* key for the Object store is called save
*/
indexedDbRequest = window.indexedDB.open("bitburnerSave", 1);
indexedDbRequest.onerror = function (e) {
console.error("Error opening indexedDB: ");
console.error(e);
Engine.load(null); // Try to load from localstorage
cb();
};
indexedDbRequest.onsuccess = function (e) {
Engine.indexedDb = e.target.result;
var transaction = Engine.indexedDb.transaction(["savestring"]);
var objectStore = transaction.objectStore("savestring");
var request = objectStore.get("save");
request.onerror = function (e) {
console.error("Error in Database request to get savestring: " + e);
Engine.load(null); // Try to load from localstorage
cb();
};
request.onsuccess = function () {
Engine.load(request.result);
cb();
};
};
indexedDbRequest.onupgradeneeded = function (e) {
const db = e.target.result;
db.createObjectStore("savestring");
};
}
var indexedDbRequest;
export { Engine, load };

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { TTheme as Theme } from "./ui/React/Theme"; import { TTheme as Theme, colors } from "./ui/React/Theme";
import { LoadingScreen } from "./ui/LoadingScreen"; import { LoadingScreen } from "./ui/LoadingScreen";
import "./engineStyle"; import "./engineStyle";
@ -11,3 +11,14 @@ ReactDOM.render(
</Theme>, </Theme>,
document.getElementById("mainmenu-container"), document.getElementById("mainmenu-container"),
); );
// setTimeout(() => {
// colors.primary = "#fff";
// refreshTheme();
// ReactDOM.render(
// <Theme>
// <LoadingScreen />
// </Theme>,
// document.getElementById("mainmenu-container"),
// );
// }, 5000);

@ -4,6 +4,7 @@
*/ */
import * as React from "react"; import * as React from "react";
import { Money } from "../React/Money";
import { MoneyRate } from "../React/MoneyRate"; import { MoneyRate } from "../React/MoneyRate";
import { use } from "../Context"; import { use } from "../Context";
@ -40,18 +41,13 @@ export function ScriptProduction(): React.ReactElement {
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}> <TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography variant="body2">Total online production of Active scripts:</Typography> <Typography variant="body2">Total production:</Typography>
</TableCell> </TableCell>
<TableCell align="left" classes={{ root: classes.cell }}> <TableCell align="left" classes={{ root: classes.cell }}>
<Typography variant="body2"> <Typography variant="body2">
<MoneyRate money={player.scriptProdSinceLastAug} /> <Money money={player.scriptProdSinceLastAug} />
</Typography> </Typography>
</TableCell> </TableCell>
</TableRow>
<TableRow style={{ width: "1px" }}>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography variant="body2">Total online production since last Aug installation:</Typography>
</TableCell>
<TableCell align="left" classes={{ root: classes.cell }}> <TableCell align="left" classes={{ root: classes.cell }}>
<Typography variant="body2"> <Typography variant="body2">
(<MoneyRate money={prodRateSinceLastAug} />) (<MoneyRate money={prodRateSinceLastAug} />)

@ -4,6 +4,7 @@ import { WorkerScriptAccordion } from "./WorkerScriptAccordion";
import List from "@mui/material/List"; import List from "@mui/material/List";
import TablePagination from "@mui/material/TablePagination"; import TablePagination from "@mui/material/TablePagination";
import { TablePaginationActionsAll } from "../React/TablePaginationActionsAll"; import { TablePaginationActionsAll } from "../React/TablePaginationActionsAll";
import { Settings } from "../../Settings/Settings";
interface IProps { interface IProps {
workerScripts: WorkerScript[]; workerScripts: WorkerScript[];
@ -11,36 +12,30 @@ interface IProps {
export function ServerAccordionContent(props: IProps): React.ReactElement { export function ServerAccordionContent(props: IProps): React.ReactElement {
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10); const [rowsPerPage, setRowsPerPage] = useState(Settings.ActiveScriptsScriptPageSize);
const handleChangePage = (event: unknown, newPage: number): void => { const handleChangePage = (event: unknown, newPage: number): void => {
setPage(newPage); setPage(newPage);
}; };
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>): void => {
Settings.ActiveScriptsScriptPageSize = parseInt(event.target.value, 10);
setRowsPerPage(parseInt(event.target.value, 10)); setRowsPerPage(parseInt(event.target.value, 10));
setPage(0); setPage(0);
}; };
let safePage = page;
while (safePage * rowsPerPage + 1 > props.workerScripts.length) {
safePage--;
}
if (safePage != page) setPage(safePage);
return ( return (
<> <>
<List dense disablePadding> <List dense disablePadding>
{props.workerScripts.slice(safePage * rowsPerPage, safePage * rowsPerPage + rowsPerPage).map((ws) => ( {props.workerScripts.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((ws) => (
<WorkerScriptAccordion key={`${ws.name}_${ws.args}`} workerScript={ws} /> <WorkerScriptAccordion key={`${ws.name}_${ws.args}`} workerScript={ws} />
))} ))}
</List> </List>
<TablePagination <TablePagination
rowsPerPageOptions={[10, 15, 20]} rowsPerPageOptions={[10, 15, 20, 100]}
component="div" component="div"
count={props.workerScripts.length} count={props.workerScripts.length}
rowsPerPage={rowsPerPage} rowsPerPage={rowsPerPage}
page={safePage} page={page}
onPageChange={handleChangePage} onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage} onRowsPerPageChange={handleChangeRowsPerPage}
ActionsComponent={TablePaginationActionsAll} ActionsComponent={TablePaginationActionsAll}

@ -13,6 +13,7 @@ import { WorkerScript } from "../../Netscript/WorkerScript";
import { WorkerScriptStartStopEventEmitter } from "../../Netscript/WorkerScriptStartStopEventEmitter"; import { WorkerScriptStartStopEventEmitter } from "../../Netscript/WorkerScriptStartStopEventEmitter";
import { getServer } from "../../Server/ServerHelpers"; import { getServer } from "../../Server/ServerHelpers";
import { BaseServer } from "../../Server/BaseServer"; import { BaseServer } from "../../Server/BaseServer";
import { Settings } from "../../Settings/Settings";
import { TablePaginationActionsAll } from "../React/TablePaginationActionsAll"; import { TablePaginationActionsAll } from "../React/TablePaginationActionsAll";
import SearchIcon from "@mui/icons-material/Search"; import SearchIcon from "@mui/icons-material/Search";
@ -33,7 +34,7 @@ type IProps = {
export function ServerAccordions(props: IProps): React.ReactElement { export function ServerAccordions(props: IProps): React.ReactElement {
const [filter, setFilter] = useState(""); const [filter, setFilter] = useState("");
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10); const [rowsPerPage, setRowsPerPage] = useState(Settings.ActiveScriptsServerPageSize);
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
const handleChangePage = (event: unknown, newPage: number): void => { const handleChangePage = (event: unknown, newPage: number): void => {
@ -41,6 +42,7 @@ export function ServerAccordions(props: IProps): React.ReactElement {
}; };
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>): void => {
Settings.ActiveScriptsServerPageSize = parseInt(event.target.value, 10);
setRowsPerPage(parseInt(event.target.value, 10)); setRowsPerPage(parseInt(event.target.value, 10));
setPage(0); setPage(0);
}; };
@ -74,13 +76,6 @@ export function ServerAccordions(props: IProps): React.ReactElement {
function rerender(): void { function rerender(): void {
setRerender((old) => !old); setRerender((old) => !old);
let safePage = page;
while (safePage * rowsPerPage + 1 >= filtered.length) {
safePage--;
}
if (safePage != page) setPage(safePage);
} }
useEffect(() => WorkerScriptStartStopEventEmitter.subscribe(rerender)); useEffect(() => WorkerScriptStartStopEventEmitter.subscribe(rerender));
@ -108,7 +103,7 @@ export function ServerAccordions(props: IProps): React.ReactElement {
})} })}
</List> </List>
<TablePagination <TablePagination
rowsPerPageOptions={[10, 15, 20]} rowsPerPageOptions={[10, 15, 20, 100]}
component="div" component="div"
count={filtered.length} count={filtered.length}
rowsPerPage={rowsPerPage} rowsPerPage={rowsPerPage}

@ -4,7 +4,7 @@ import { IPlayer } from "../PersonObjects/IPlayer";
import { IEngine } from "../IEngine"; import { IEngine } from "../IEngine";
import { ITerminal } from "../Terminal/ITerminal"; import { ITerminal } from "../Terminal/ITerminal";
import { installAugmentations } from "../Augmentation/AugmentationHelpers"; import { installAugmentations } from "../Augmentation/AugmentationHelpers";
import { saveObject, openImportFileHandler } from "../SaveObject"; import { saveObject } from "../SaveObject";
import { onExport } from "../ExportBonus"; import { onExport } from "../ExportBonus";
import { LocationName } from "../Locations/data/LocationNames"; import { LocationName } from "../Locations/data/LocationNames";
import { Location } from "../Locations/Location"; import { Location } from "../Locations/Location";
@ -282,7 +282,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
<Context.Router.Provider value={Router}> <Context.Router.Provider value={Router}>
<Overview> <Overview>
{!ITutorial.isRunning ? ( {!ITutorial.isRunning ? (
<CharacterOverview save={() => saveObject.saveGame(engine.indexedDb)} /> <CharacterOverview save={() => saveObject.saveGame()} />
) : ( ) : (
<InteractiveTutorialRoot /> <InteractiveTutorialRoot />
)} )}
@ -300,7 +300,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
) : ( ) : (
<Box display="flex" flexDirection="row" width="100%"> <Box display="flex" flexDirection="row" width="100%">
<SidebarRoot player={player} router={Router} page={page} /> <SidebarRoot player={player} router={Router} page={page} />
<Box className={classes.root} flexGrow={1} display="block" width="100%" px={1} height="100vh"> <Box className={classes.root} flexGrow={1} display="block" px={1} height="100vh">
{page === Page.Terminal ? ( {page === Page.Terminal ? (
<TerminalRoot terminal={terminal} router={Router} player={player} /> <TerminalRoot terminal={terminal} router={Router} player={player} />
) : page === Page.Sleeves ? ( ) : page === Page.Sleeves ? (
@ -357,10 +357,8 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
) : page === Page.Options ? ( ) : page === Page.Options ? (
<GameOptionsRoot <GameOptionsRoot
player={player} player={player}
save={() => saveObject.saveGame(engine.indexedDb)} save={() => saveObject.saveGame()}
delete={() => saveObject.deleteGame(engine.indexedDb)}
export={() => saveObject.exportGame()} export={() => saveObject.exportGame()}
import={openImportFileHandler}
forceKill={() => { forceKill={() => {
for (const hostname of Object.keys(AllServers)) { for (const hostname of Object.keys(AllServers)) {
AllServers[hostname].runningScripts = []; AllServers[hostname].runningScripts = [];

@ -535,7 +535,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
const content = contents[step]; const content = contents[step];
if (content === undefined) throw new Error("error in the tutorial"); if (content === undefined) throw new Error("error in the tutorial");
return ( return (
<Paper square sx={{ maxWidth: "35vh", p: 2 }}> <Paper square sx={{ maxWidth: "70vw", p: 2 }}>
{content.content} {content.content}
{step !== iTutorialSteps.TutorialPageInfo && ( {step !== iTutorialSteps.TutorialPageInfo && (
<> <>

@ -4,56 +4,13 @@ import Typography from "@mui/material/Typography";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import { Terminal } from "../Terminal"; import { Terminal } from "../Terminal";
import { Engine } from "../engine"; import { load } from "../db";
import { Player } from "../Player"; import { Player } from "../Player";
import { Engine } from "../engine";
import { GameRoot } from "./GameRoot"; import { GameRoot } from "./GameRoot";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
function load(cb: () => void): void {
if (!window.indexedDB) {
return Engine.load(""); // Will try to load from localstorage
}
/**
* DB is called bitburnerSave
* Object store is called savestring
* key for the Object store is called save
*/
// Version 1 is important
const indexedDbRequest: IDBOpenDBRequest = window.indexedDB.open("bitburnerSave", 1);
indexedDbRequest.onerror = function (this: IDBRequest<IDBDatabase>, ev: Event) {
console.error("Error opening indexedDB: ");
console.error(ev);
Engine.load(""); // Try to load from localstorage
cb();
};
indexedDbRequest.onsuccess = function (this: IDBRequest<IDBDatabase>) {
Engine.indexedDb = this.result;
const transaction = Engine.indexedDb.transaction(["savestring"]);
const objectStore = transaction.objectStore("savestring");
const request: IDBRequest<string> = objectStore.get("save");
request.onerror = function (this: IDBRequest<string>, ev: Event) {
console.error("Error in Database request to get savestring: " + ev);
Engine.load(""); // Try to load from localstorage
cb();
};
request.onsuccess = function (this: IDBRequest<string>) {
Engine.load(this.result);
cb();
};
};
// This is called when there's no db to begin with. It's important.
indexedDbRequest.onupgradeneeded = function (this: IDBRequest<IDBDatabase>) {
const db = this.result;
db.createObjectStore("savestring");
};
}
export function LoadingScreen(): React.ReactElement { export function LoadingScreen(): React.ReactElement {
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const [loaded, setLoaded] = useState(false); const [loaded, setLoaded] = useState(false);
@ -66,9 +23,19 @@ export function LoadingScreen(): React.ReactElement {
}); });
useEffect(() => { useEffect(() => {
load(() => { async function doLoad() {
setLoaded(true); await load()
}); .then((saveString) => {
Engine.load(saveString);
setLoaded(true);
})
.catch((reason) => {
console.error(reason);
Engine.load("");
setLoaded(true);
});
}
doLoad();
}, []); }, []);
if (loaded) { if (loaded) {

@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
export function Augmentation(name: string): JSX.Element { export function Augmentation({ name }: { name: string }): JSX.Element {
return ( return (
<span className={"samefont"} style={{ color: "white" }}> <span className={"samefont"} style={{ color: "white" }}>
{name} {name}

@ -1,7 +1,9 @@
// Root React Component for the Corporation UI // Root React Component for the Corporation UI
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Theme } from "@mui/material/styles";
import makeStyles from "@mui/styles/makeStyles"; import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { Reputation } from "./Reputation"; import { Reputation } from "./Reputation";
@ -78,35 +80,37 @@ function Work(): React.ReactElement {
); );
} }
const useStyles = makeStyles({ const useStyles = makeStyles((theme: Theme) =>
cellNone: { createStyles({
borderBottom: "none", cellNone: {
padding: 0, borderBottom: "none",
margin: 0, padding: 0,
}, margin: 0,
cell: { },
padding: 0, cell: {
margin: 0, padding: 0,
}, margin: 0,
hp: { },
color: colors.hp, hp: {
}, color: theme.colors.hp,
money: { },
color: colors.money, money: {
}, color: theme.colors.money,
hack: { },
color: colors.hack, hack: {
}, color: theme.colors.hack,
combat: { },
color: colors.combat, combat: {
}, color: theme.colors.combat,
cha: { },
color: colors.cha, cha: {
}, color: theme.colors.cha,
int: { },
color: colors.int, int: {
}, color: theme.colors.int,
}); },
}),
);
export function CharacterOverview({ save }: IProps): React.ReactElement { export function CharacterOverview({ save }: IProps): React.ReactElement {
const player = use.Player(); const player = use.Player();

@ -28,6 +28,7 @@ import { dialogBoxCreate } from "../../../utils/DialogBox";
import { ConfirmationModal } from "./ConfirmationModal"; import { ConfirmationModal } from "./ConfirmationModal";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { save, deleteGame } from "../../db";
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@ -42,9 +43,7 @@ const useStyles = makeStyles((theme: Theme) =>
interface IProps { interface IProps {
player: IPlayer; player: IPlayer;
save: () => void; save: () => void;
delete: () => void;
export: () => void; export: () => void;
import: (evt: any) => void;
forceKill: () => void; forceKill: () => void;
softReset: () => void; softReset: () => void;
} }
@ -153,20 +152,38 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
Settings.Locale = event.target.value as string; Settings.Locale = event.target.value as string;
} }
function importSave(): void { function startImport(): void {
if (window.File && window.FileReader && window.FileList && window.Blob) { if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;
// var fileSelector = clearEventListeners("import-game-file-selector"); const ii = importInput.current;
// fileSelector.addEventListener("change", openImportFileHandler, false); if (ii === null) throw new Error("import input should not be null");
const ii = importInput.current; ii.click();
if (ii === null) throw new Error("import input should not be null");
ii.click();
} else {
dialogBoxCreate("ERR: Your browser does not support HTML5 File API. Cannot import.");
}
} }
function onImport(event: React.ChangeEvent<HTMLInputElement>): void { function onImport(event: React.ChangeEvent<HTMLInputElement>): void {
props.import(event); const files = event.target.files;
if (files === null) return;
const file = files[0];
if (!file) {
dialogBoxCreate("Invalid file selected");
return;
}
const reader = new FileReader();
reader.onload = function (this: FileReader, e: ProgressEvent<FileReader>) {
const target = e.target;
if (target === null) {
console.error("error importing file");
return;
}
const result = target.result;
if (typeof result !== "string" || result === null) {
console.error("FileReader event was not type string");
return;
}
const contents = result;
save(contents).then(() => location.reload());
};
reader.readAsText(file);
} }
return ( return (
@ -490,7 +507,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip title={<Typography>import</Typography>}> <Tooltip title={<Typography>import</Typography>}>
<Button onClick={importSave}> <Button onClick={startImport}>
<UploadIcon color="primary" /> <UploadIcon color="primary" />
Import Import
<input ref={importInput} id="import-game-file-selector" type="file" hidden onChange={onImport} /> <input ref={importInput} id="import-game-file-selector" type="file" hidden onChange={onImport} />
@ -536,6 +553,9 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
</Tooltip> </Tooltip>
</Box> </Box>
<Box> <Box>
<Link href="https://github.com/danielyxie/bitburner/issues/new" target="_blank">
<Typography>Report bug</Typography>
</Link>
<Link href="https://bitburner.readthedocs.io/en/latest/changelog.html" target="_blank"> <Link href="https://bitburner.readthedocs.io/en/latest/changelog.html" target="_blank">
<Typography>Changelog</Typography> <Typography>Changelog</Typography>
</Link> </Link>
@ -554,8 +574,10 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
<FileDiagnosticModal open={diagnosticOpen} onClose={() => setDiagnosticOpen(false)} /> <FileDiagnosticModal open={diagnosticOpen} onClose={() => setDiagnosticOpen(false)} />
<ConfirmationModal <ConfirmationModal
onConfirm={() => { onConfirm={() => {
props.delete();
setDeleteOpen(false); setDeleteOpen(false);
deleteGame()
.then(() => location.reload())
.catch((r) => console.error(`Could not delete game: ${r}`));
}} }}
open={deleteGameOpen} open={deleteGameOpen}
onClose={() => setDeleteOpen(false)} onClose={() => setDeleteOpen(false)}

@ -5,6 +5,7 @@ import Box from "@mui/material/Box";
import Collapse from "@mui/material/Collapse"; import Collapse from "@mui/material/Collapse";
import Fab from "@mui/material/Fab"; import Fab from "@mui/material/Fab";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import VisibilityIcon from "@mui/icons-material/Visibility";
import { use } from "../Context"; import { use } from "../Context";
import { Page } from "../Router"; import { Page } from "../Router";
@ -24,13 +25,19 @@ export function Overview({ children }: IProps): React.ReactElement {
const router = use.Router(); const router = use.Router();
if (router.page() === Page.BitVerse || router.page() === Page.HackingMission || router.page() === Page.Loading) if (router.page() === Page.BitVerse || router.page() === Page.HackingMission || router.page() === Page.Loading)
return <></>; return <></>;
let icon;
if (open){
icon = <VisibilityOffIcon color="primary" />;
} else {
icon = <VisibilityIcon color="primary" />;
}
return ( return (
<div style={{ position: "fixed", top: 0, right: 0, zIndex: 1500 }}> <div style={{ position: "fixed", top: 0, right: 0, zIndex: 1500 }}>
<Box display="flex" justifyContent="flex-end" flexDirection={"column"}> <Box display="flex" justifyContent="flex-end" flexDirection={"column"}>
<Collapse in={open}>{children}</Collapse> <Collapse in={open}>{children}</Collapse>
<Box display="flex" justifyContent="flex-end"> <Box display="flex" justifyContent="flex-end">
<Fab classes={{ root: classes.nobackground }} onClick={() => setOpen((old) => !old)}> <Fab classes={{ root: classes.nobackground }} onClick={() => setOpen((old) => !old)}>
<VisibilityOffIcon color="primary" /> {icon}
</Fab> </Fab>
</Box> </Box>
</Box> </Box>

@ -1,12 +1,31 @@
import React from "react"; import React from "react";
import { createTheme, ThemeProvider, Theme, StyledEngineProvider } from "@mui/material/styles"; import { createTheme, ThemeProvider, Theme, StyledEngineProvider } from "@mui/material/styles";
declare module "@mui/styles/defaultTheme" { declare module "@mui/material/styles" {
// eslint-disable-next-line @typescript-eslint/no-empty-interface interface Theme {
interface DefaultTheme extends Theme {} colors: {
hp: React.CSSProperties["color"];
money: React.CSSProperties["color"];
hack: React.CSSProperties["color"];
combat: React.CSSProperties["color"];
cha: React.CSSProperties["color"];
int: React.CSSProperties["color"];
rep: React.CSSProperties["color"];
};
}
interface ThemeOptions {
colors: {
hp: React.CSSProperties["color"];
money: React.CSSProperties["color"];
hack: React.CSSProperties["color"];
combat: React.CSSProperties["color"];
cha: React.CSSProperties["color"];
int: React.CSSProperties["color"];
rep: React.CSSProperties["color"];
};
}
} }
export let colors = {
export const colors = {
primarylight: "#0f0", primarylight: "#0f0",
primary: "#0c0", primary: "#0c0",
primarydark: "#090", primarydark: "#090",
@ -38,227 +57,242 @@ export const colors = {
combat: "#faffdf", combat: "#faffdf",
cha: "#a671d1", cha: "#a671d1",
int: "#6495ed", int: "#6495ed",
rep: "#faffdf",
}; };
export const theme = createTheme({ let theme: Theme;
palette: {
primary: { function refreshTheme() {
light: colors.primarylight, theme = createTheme({
main: colors.primary, colors: {
dark: colors.primarydark, hp: "#dd3434",
money: "#ffd700",
hack: "#adff2f",
combat: "#faffdf",
cha: "#a671d1",
int: "#6495ed",
rep: "#faffdf",
}, },
secondary: { palette: {
light: colors.secondarylight, primary: {
main: colors.secondary, light: colors.primarylight,
dark: colors.secondarydark, main: colors.primary,
dark: colors.primarydark,
},
secondary: {
light: colors.secondarylight,
main: colors.secondary,
dark: colors.secondarydark,
},
error: {
light: colors.errorlight,
main: colors.error,
dark: colors.errordark,
},
info: {
light: colors.infolight,
main: colors.info,
dark: colors.infodark,
},
warning: {
light: colors.warninglight,
main: colors.warning,
dark: colors.warningdark,
},
background: {
default: colors.black,
paper: colors.well,
},
}, },
error: { typography: {
light: colors.errorlight, fontFamily: "monospace",
main: colors.error, button: {
dark: colors.errordark, textTransform: "none",
},
}, },
info: { components: {
light: colors.infolight, MuiInputBase: {
main: colors.info, styleOverrides: {
dark: colors.infodark, root: {
}, backgroundColor: colors.well,
warning: { color: colors.primary,
light: colors.warninglight, },
main: colors.warning, input: {
dark: colors.warningdark, "&::placeholder": {
}, userSelect: "none",
background: { color: colors.primarydark,
default: colors.black, },
paper: colors.well, },
},
},
typography: {
fontFamily: "monospace",
button: {
textTransform: "none",
},
},
components: {
MuiInputBase: {
styleOverrides: {
root: {
backgroundColor: colors.well,
color: colors.primary,
}, },
input: { },
"&::placeholder": {
MuiInput: {
styleOverrides: {
root: {
backgroundColor: colors.well,
borderBottomColor: "#fff",
},
underline: {
"&:hover": {
borderBottomColor: colors.primarydark,
},
"&:before": {
borderBottomColor: colors.primary,
},
"&:after": {
borderBottomColor: colors.primarylight,
},
},
},
},
MuiInputLabel: {
styleOverrides: {
root: {
color: colors.primarydark, // why is this switched?
userSelect: "none", userSelect: "none",
color: colors.primarydark, "&:before": {
color: colors.primarylight,
},
}, },
}, },
}, },
}, MuiButton: {
styleOverrides: {
root: {
backgroundColor: "#333",
border: "1px solid " + colors.well,
// color: colors.primary,
"&:hover": {
backgroundColor: colors.black,
},
MuiInput: { borderRadius: 0,
styleOverrides: {
root: {
backgroundColor: colors.well,
borderBottomColor: "#fff",
},
underline: {
"&:hover": {
borderBottomColor: colors.primarydark,
},
"&:before": {
borderBottomColor: colors.primary,
},
"&:after": {
borderBottomColor: colors.primarylight,
}, },
}, },
}, },
}, MuiSelect: {
styleOverrides: {
MuiInputLabel: { icon: {
styleOverrides: { color: colors.primary,
root: {
color: colors.primarydark, // why is this switched?
userSelect: "none",
"&:before": {
color: colors.primarylight,
}, },
}, },
}, },
}, MuiMenu: {
MuiButton: { styleOverrides: {
styleOverrides: { list: {
root: { backgroundColor: colors.well,
backgroundColor: "#333", },
border: "1px solid " + colors.well, },
// color: colors.primary, },
margin: "5px", MuiMenuItem: {
padding: "3px 5px", styleOverrides: {
"&:hover": { root: {
color: colors.primary,
},
},
},
MuiAccordionSummary: {
styleOverrides: {
root: {
backgroundColor: "#111",
},
},
},
MuiAccordionDetails: {
styleOverrides: {
root: {
backgroundColor: colors.black, backgroundColor: colors.black,
}, },
borderRadius: 0,
}, },
}, },
}, MuiIconButton: {
MuiSelect: { styleOverrides: {
styleOverrides: { root: {
icon: { color: colors.primary,
color: colors.primary,
},
},
},
MuiMenu: {
styleOverrides: {
list: {
backgroundColor: colors.well,
},
},
},
MuiMenuItem: {
styleOverrides: {
root: {
color: colors.primary,
},
},
},
MuiAccordionSummary: {
styleOverrides: {
root: {
backgroundColor: "#111",
},
},
},
MuiAccordionDetails: {
styleOverrides: {
root: {
backgroundColor: colors.black,
},
},
},
MuiIconButton: {
styleOverrides: {
root: {
color: colors.primary,
},
},
},
MuiTooltip: {
styleOverrides: {
tooltip: {
fontSize: "1em",
color: colors.primary,
backgroundColor: colors.well,
borderRadius: 0,
border: "2px solid white",
},
},
},
MuiSlider: {
styleOverrides: {
valueLabel: {
color: colors.primary,
backgroundColor: colors.well,
},
},
},
MuiDrawer: {
styleOverrides: {
paper: {
"&::-webkit-scrollbar": {
// webkit
display: "none",
}, },
scrollbarWidth: "none", // firefox
backgroundColor: colors.black,
}, },
paperAnchorDockedLeft: { },
borderRight: "1px solid " + colors.welllight, MuiTooltip: {
styleOverrides: {
tooltip: {
fontSize: "1em",
color: colors.primary,
backgroundColor: colors.well,
borderRadius: 0,
border: "2px solid white",
maxWidth: "100vh",
},
},
},
MuiSlider: {
styleOverrides: {
valueLabel: {
color: colors.primary,
backgroundColor: colors.well,
},
},
},
MuiDrawer: {
styleOverrides: {
paper: {
"&::-webkit-scrollbar": {
// webkit
display: "none",
},
scrollbarWidth: "none", // firefox
backgroundColor: colors.black,
},
paperAnchorDockedLeft: {
borderRight: "1px solid " + colors.welllight,
},
},
},
MuiDivider: {
styleOverrides: {
root: {
backgroundColor: colors.welllight,
},
},
},
MuiFormControlLabel: {
styleOverrides: {
root: {
color: colors.primary,
},
},
},
MuiSwitch: {
styleOverrides: {
switchBase: {
color: colors.primarydark,
},
track: {
backgroundColor: colors.welllight,
},
},
},
MuiPaper: {
styleOverrides: {
root: {
borderRadius: 0,
backgroundColor: colors.black,
border: "1px solid " + colors.welllight,
},
},
},
MuiTablePagination: {
styleOverrides: {
select: {
color: colors.primary,
},
}, },
}, },
}, },
MuiDivider: { });
styleOverrides: { console.log("refreshed");
root: { }
backgroundColor: colors.welllight, refreshTheme();
},
},
},
MuiFormControlLabel: {
styleOverrides: {
root: {
color: colors.primary,
},
},
},
MuiSwitch: {
styleOverrides: {
switchBase: {
color: colors.primarydark,
},
track: {
backgroundColor: colors.welllight,
},
},
},
MuiPaper: {
styleOverrides: {
root: {
borderRadius: 0,
backgroundColor: colors.black,
border: "1px solid " + colors.welllight,
},
},
},
MuiTablePagination: {
styleOverrides: {
select: {
color: colors.primary,
},
},
},
},
});
interface IProps { interface IProps {
children: JSX.Element[] | JSX.Element; children: JSX.Element[] | JSX.Element;

@ -32,11 +32,11 @@ export function WorkInProgressRoot(): React.ReactElement {
const faction = Factions[player.currentWorkFactionName]; const faction = Factions[player.currentWorkFactionName];
if (player.workType == CONSTANTS.WorkTypeFaction) { if (player.workType == CONSTANTS.WorkTypeFaction) {
function cancel(): void { function cancel(): void {
router.toFaction(); router.toFaction(faction);
player.finishFactionWork(true); player.finishFactionWork(true);
} }
function unfocus(): void { function unfocus(): void {
router.toFaction(); router.toFaction(faction);
player.stopFocusing(); player.stopFocusing();
} }
return ( return (

@ -11,10 +11,6 @@
* This formula ensures that the effects of the statistic that is being processed * This formula ensures that the effects of the statistic that is being processed
* has diminishing returns, but never loses its effectiveness as you continue * has diminishing returns, but never loses its effectiveness as you continue
* to raise it. * to raise it.
*
* There are two implementations of this component. One is simply a function that
* can be called with the stat and the exponential/linear factors. The other is a
* class where the exponential and linear factors are defined upon construction.
*/ */
export function calculateEffectWithFactors(n: number, expFac: number, linearFac: number): number { export function calculateEffectWithFactors(n: number, expFac: number, linearFac: number): number {
if (expFac <= 0 || expFac >= 1) { if (expFac <= 0 || expFac >= 1) {
@ -26,20 +22,3 @@ export function calculateEffectWithFactors(n: number, expFac: number, linearFac:
return Math.pow(n, expFac) + n / linearFac; return Math.pow(n, expFac) + n / linearFac;
} }
export class EffectWithFactors {
// Exponential factor
private expFac: number;
// Linear Factor
private linearFac: number;
constructor(expFac: number, linearFac: number) {
this.expFac = expFac;
this.linearFac = linearFac;
}
calculate(n: number): number {
return calculateEffectWithFactors(n, this.expFac, this.linearFac);
}
}

@ -6,6 +6,7 @@ const HtmlWebpackPlugin = require("html-webpack-plugin");
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
const UnusedWebpackPlugin = require("unused-webpack-plugin"); const UnusedWebpackPlugin = require("unused-webpack-plugin");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin"); const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const DeadCodePlugin = require("webpack-deadcode-plugin");
module.exports = (env, argv) => { module.exports = (env, argv) => {
const isDevServer = (env || {}).devServer === true; const isDevServer = (env || {}).devServer === true;
@ -130,6 +131,10 @@ module.exports = (env, argv) => {
module: true, module: true,
}), }),
isDevelopment && new ReactRefreshWebpackPlugin(), isDevelopment && new ReactRefreshWebpackPlugin(),
new DeadCodePlugin({
patterns: ["src/**/*.(js|jsx|css|ts|tsx)"],
exclude: ["**/*.(stories|spec).(js|jsx)"],
}),
].filter(Boolean), ].filter(Boolean),
target: "web", target: "web",
entry: entry, entry: entry,