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
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:
.. code-block:: javascript

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

@ -3,7 +3,9 @@ getAugmentationCost() Netscript Function
.. 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

@ -1,4 +1,5 @@
const { app, BrowserWindow, Menu } = require("electron");
const { app, BrowserWindow, Menu, globalShortcut, shell } = require("electron");
Menu.setApplicationMenu(false);
function createWindow() {
const win = new BrowserWindow({
@ -12,6 +13,24 @@ function createWindow() {
win.maximize();
win.loadFile("index.html");
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(() => {

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",
"unused-webpack-plugin": "^2.4.0",
"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",
"devDependencies": {

@ -1,14 +1,19 @@
# npm install electron --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 electron/* .package
cp dist/engine.bundle.js .package/dist
cp dist/engineStyle.css .package/dist
# The css files
cp dist/vendor.css .package/dist
cp dist/engineStyle.bundle.js .package/dist
cp dist/vendor.bundle.js .package/dist
cp main.css .package/main.css
# 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

@ -211,7 +211,7 @@ function initAugmentations() {
name: AugmentationNames.Targeting3,
moneyCost: 1.15e8,
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],
dexterity_mult: 1.3,
});

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

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

@ -3,6 +3,7 @@ import { removePopup } from "../../ui/React/createPopup";
import { IBladeburner } from "../IBladeburner";
import { WorldMap } from "../../ui/React/WorldMap";
import { CityName } from "../../Locations/data/CityNames";
import { Settings } from "../../Settings/Settings";
interface IProps {
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
for your Bladeburner duties does not affect your location in the game otherwise.
</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) {
playerWin = false;
while (this.state.strategy.match(n)) {
n++;
n = (n + 1) % 36;
}
}
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";
// 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 {
// 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 { Money } from "../../ui/React/Money";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { IRouter } from "../../ui/Router";
interface IProps {
player: IPlayer;
popupId: string;
router: IRouter;
}
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.",
);
removePopup(props.popupId);
props.router.toCorporation();
}
function seed(): void {
@ -55,6 +58,7 @@ export function CreateCorporationPopup(props: IProps): React.ReactElement {
"You can visit and manage your company in the City.",
);
removePopup(props.popupId);
props.router.toCorporation();
}
return (

@ -95,7 +95,7 @@ function BulkPurchase(props: IProps): React.ReactElement {
style={{ margin: "5px" }}
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]);
} catch (err) {
dialogBoxCreate(err + "");
return;
}
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 {
for (const key in DarkWebItems) {
const item = DarkWebItems[key];

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

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

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

@ -1,7 +1,7 @@
import { Player } from "../Player";
import { Exploit } from "./Exploit";
(function () {
export function startUnclickable(): void {
function clickTheUnclickable(event: MouseEvent): void {
if (!event.target || !(event.target instanceof Element)) return;
const display = window.getComputedStyle(event.target as Element).display;
@ -19,4 +19,4 @@ import { Exploit } from "./Exploit";
}
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 { 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 = {
faction: Faction;
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)),
);
const purchaseableAugmentation = (aug: string): React.ReactNode => {
return <PurchaseableAugmentation augName={aug} faction={props.faction} key={aug} p={player} rerender={rerender} />;
const purchaseableAugmentation = (aug: string, owned = false): React.ReactNode => {
return (
<PurchaseableAugmentation
augName={aug}
faction={props.faction}
key={aug}
p={player}
rerender={rerender}
owned={owned}
/>
);
};
const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug));
@ -116,42 +130,33 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
ownedElem = (
<>
<br />
<h2>Purchased Augmentations</h2>
<p>This factions also offers these augmentations but you already own them.</p>
{owned.map((aug) => purchaseableAugmentation(aug))}
<Typography variant="h4">Purchased Augmentations</Typography>
<Typography>This factions also offers these augmentations but you already own them.</Typography>
{owned.map((aug) => purchaseableAugmentation(aug, true))}
</>
);
}
return (
<div>
<StdButton onClick={props.routeToMainPage} text={"Back"} />
<h1>Faction Augmentations</h1>
<p>
<Button onClick={props.routeToMainPage}>Back</Button>
<Typography variant="h4">Faction Augmentations</Typography>
<Typography>
These are all of the Augmentations that are available to purchase from {props.faction.name}. Augmentations are
powerful upgrades that will enhance your abilities.
</p>
<StdButton onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)} text={"Sort by Cost"} />
<StdButton
onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}
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 />
</Typography>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}>Sort by Reputation</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}>Sort by Default Order</Button>
<br />
<Table size="small" padding="none">
<TableBody>{augListElems}</TableBody>
</Table>
<Table size="small" padding="none">
<TableBody>{ownedElem}</TableBody>
</Table>
</div>
);
}

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

@ -21,6 +21,9 @@ import { createPopup } from "../../ui/React/createPopup";
import { use } from "../../ui/Context";
import { CreateGangPopup } from "./CreateGangPopup";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
type IProps = {
faction: Faction;
};
@ -145,8 +148,11 @@ export function FactionRoot(props: IProps): React.ReactElement {
}
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} />
{canAccessGang && <Option buttonText={"Manage Gang"} infoText={gangInfo} onClick={() => manageGang(faction)} />}
{!isPlayersGang && factionInfo.offerHackingMission && (
@ -186,7 +192,7 @@ export function FactionRoot(props: IProps): React.ReactElement {
onClick={sleevePurchases}
/>
)}
</div>
</>
);
}

@ -13,85 +13,79 @@ import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor";
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 = {
faction: Faction;
factionInfo: FactionInfo;
};
type IInnerHTMLMarkup = {
__html: string;
};
const useStyles = makeStyles((theme: Theme) =>
createStyles({
noformat: {
whiteSpace: "pre-wrap",
},
}),
);
const blockStyleMarkup = {
display: "block",
};
export function Info(props: IProps): React.ReactElement {
const classes = useStyles();
const infoStyleMarkup = {
display: "block",
width: "70%",
};
const favorGain = props.faction.getFavorGain()[0];
return (
<>
<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> {
constructor(props: IProps) {
super(props);
<Typography>-------------------------</Typography>
this.getFavorGainContent = this.getFavorGainContent.bind(this);
this.getReputationContent = this.getReputationContent.bind(this);
}
<Box display="flex">
<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 {
const favorGain = this.props.faction.getFavorGain()[0];
return (
<>
You will have {Favor(this.props.faction.favor + favorGain)} faction favor after installing an Augmentation.
<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}`}
/>
</>
);
}
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>
);
}
<Typography>-------------------------</Typography>
<Typography>
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.
</Typography>
</>
);
}

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

@ -19,13 +19,60 @@ import { IMap } from "../../types";
import { StdButton } from "../../ui/React/StdButton";
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;
faction: Faction;
p: IPlayer;
rerender: () => void;
};
owned?: boolean;
}
export function PurchaseableAugmentation(props: IProps): React.ReactElement {
const aug = Augmentations[props.augName];
@ -39,22 +86,6 @@ export function PurchaseableAugmentation(props: IProps): React.ReactElement {
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
function hasPrereqs(): boolean {
return hasAugmentationPrereqs(aug);
@ -94,39 +125,12 @@ export function PurchaseableAugmentation(props: IProps): React.ReactElement {
const moneyCost = getMoneyCost();
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
let disabled = false;
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;
}
const color: "error" | "primary" = !hasReq || !hasRep || !hasCost ? "error" : "primary";
// Determine button txt
let btnTxt = aug.name;
@ -135,16 +139,16 @@ export function PurchaseableAugmentation(props: IProps): React.ReactElement {
}
let tooltip = <></>;
if (typeof aug.info === "string")
if (typeof aug.info === "string") {
tooltip = (
<>
<span dangerouslySetInnerHTML={{ __html: aug.info }} />
<span>{aug.info}</span>
<br />
<br />
{aug.stats}
</>
);
else
} else
tooltip = (
<>
{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 (
<li key={aug.name}>
<span
style={{
margin: "4px",
padding: "4px",
}}
>
<StdButton
disabled={disabled}
onClick={handleClick}
style={{
display: "inline-block",
}}
text={btnTxt}
tooltip={tooltip}
<TableRow>
{!props.owned && (
<TableCell key={0}>
<Button onClick={handleClick} color={color}>
Buy
</Button>
</TableCell>
)}
<TableCell key={1}>
<Box display="flex">
<Tooltip
title={<Typography>{tooltip}</Typography>}
placement="top"
disableFocusListener
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>
</li>
)}
</TableRow>
);
}

@ -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;
}
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();
return {
hack:
this.calculateAscensionMult(this.hack_asc_points + points.hack) /
this.calculateAscensionMult(this.hack_asc_points),
str:
this.calculateAscensionMult(this.str_asc_points + points.str) /
this.calculateAscensionMult(this.str_asc_points),
def:
this.calculateAscensionMult(this.def_asc_points + points.def) /
this.calculateAscensionMult(this.def_asc_points),
dex:
this.calculateAscensionMult(this.dex_asc_points + points.dex) /
this.calculateAscensionMult(this.dex_asc_points),
agi:
this.calculateAscensionMult(this.agi_asc_points + points.agi) /
this.calculateAscensionMult(this.agi_asc_points),
cha:
this.calculateAscensionMult(this.cha_asc_points + points.cha) /
this.calculateAscensionMult(this.cha_asc_points),
};
hack: this.calculateAscensionMult(this.hack_asc_points + points.hack),
str: this.calculateAscensionMult(this.str_asc_points + points.str),
def: this.calculateAscensionMult(this.def_asc_points + points.def),
dex: this.calculateAscensionMult(this.dex_asc_points + points.dex),
agi: this.calculateAscensionMult(this.agi_asc_points + points.agi),
cha: this.calculateAscensionMult(this.cha_asc_points + points.cha),
}
}
getAscensionResults(): IMults {
const postAscend = this.getAscensionMultsAfterAscend();
const preAscend = this.getCurrentAscensionMults();
return {
hack: postAscend.hack / preAscend.hack,
str: postAscend.str / preAscend.str,
def: postAscend.def / preAscend.def,
dex: postAscend.dex / preAscend.dex,
agi: postAscend.agi / preAscend.agi,
cha: postAscend.cha / preAscend.cha,
}
}
ascend(): IAscensionResult {

@ -57,7 +57,9 @@ export function AscensionPopup(props: IProps): React.ReactElement {
removePopup(props.popupId);
}
const ascendBenefits = props.member.getAscensionResults();
// const ascendBenefits = props.member.getAscensionResults();
const preAscend = props.member.getCurrentAscensionMults();
const postAscend = props.member.getAscensionMultsAfterAscend();
return (
<>
@ -72,17 +74,17 @@ export function AscensionPopup(props: IProps): React.ReactElement {
<br />
In return, they will gain the following permanent boost to stat multipliers:
<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 />
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 />
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 />
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 />
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 />
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 />
</pre>
<button className="std-button" onClick={confirm}>

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

@ -2,7 +2,7 @@ import { makeRuntimeRejectMsg } from "./NetscriptEvaluator";
import { ScriptUrl } from "./Script/ScriptUrl";
// 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" });
}
@ -93,7 +93,7 @@ function shouldCompile(script, scripts) {
* the script parameter.
*/
// 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
/** @type {ScriptUrl[]} */
const urlStack = [];

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

@ -6,6 +6,7 @@ import { removePopup } from "../../../ui/React/createPopup";
import { Money } from "../../../ui/React/Money";
import { WorldMap } from "../../../ui/React/WorldMap";
import { CityName } from "../../../Locations/data/CityNames";
import { Settings } from "../../../Settings/Settings";
import { dialogBoxCreate } from "../../../../utils/DialogBox";
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
also set your current sleeve task to idle.
</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";
let redPillFlag = false;
function hackWorldDaemon(router, flume = false, quick = false) {
router.toBitVerse(flume, quick);
redPillFlag = true;
}
function giveSourceFile(bitNodeNumber) {
var sourceFileKey = "SourceFile" + bitNodeNumber.toString();
@ -86,4 +82,4 @@ export function enterBitNode(router, flume, destroyedBitNode, newBitNode) {
prestigeSourceFile(flume);
}
export { redPillFlag, hackWorldDaemon };
export { redPillFlag };

8
src/SaveObject.d.ts vendored

@ -1,2 +1,6 @@
export declare const saveObject: any;
export declare function openImportFileHandler(evt: any): void;
export declare const saveObject: {
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 { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../utils/JSONReviver";
import { save } from "./db";
import Decimal from "decimal.js";
@ -84,33 +85,12 @@ BitburnerSaveObject.prototype.getSaveString = function () {
return saveString;
};
BitburnerSaveObject.prototype.saveGame = function (db) {
var saveString = this.getSaveString();
BitburnerSaveObject.prototype.saveGame = function () {
const saveString = this.getSaveString();
// We'll save to both localstorage and indexedDb
var objectStore = db.transaction(["savestring"], "readwrite").objectStore("savestring");
var request = objectStore.put(saveString, "save");
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!");
save(saveString)
.then(() => createStatusText("Game saved!"))
.catch((err) => console.error(err));
};
// Makes necessary changes to the loaded/imported data to ensure
@ -155,16 +135,10 @@ function evaluateVersionCompatibility(ver) {
}
function loadGame(saveString) {
if (saveString === "" || saveString == null || saveString === undefined) {
if (!window.localStorage.getItem("bitburnerSave")) {
return false;
}
saveString = decodeURIComponent(escape(atob(window.localStorage.getItem("bitburnerSave"))));
} else {
saveString = decodeURIComponent(escape(atob(saveString)));
}
if (!saveString) return false;
saveString = decodeURIComponent(escape(atob(saveString)));
var saveObj = JSON.parse(saveString, Reviver);
const saveObj = JSON.parse(saveString, Reviver);
loadPlayer(saveObj.PlayerSave);
loadAllServers(saveObj.AllServersSave);
@ -224,19 +198,12 @@ function loadGame(saveString) {
} else {
Settings.init();
}
if (saveObj.hasOwnProperty("FconfSettingsSave")) {
try {
loadFconf(saveObj.FconfSettingsSave);
} catch (e) {
console.error("ERROR: Failed to parse .fconf Settings.");
}
}
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);
console.error("ERROR: Failed to parse last export bonus Settings " + err);
}
}
if (saveObj.hasOwnProperty("VersionSave")) {
@ -267,173 +234,6 @@ function loadGame(saveString) {
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 () {
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() {
dialogBoxCreate(
"New update!<br>" +
@ -514,19 +289,4 @@ BitburnerSaveObject.fromJSON = function (value) {
Reviver.constructors.BitburnerSaveObject = BitburnerSaveObject;
function openImportFileHandler(evt) {
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 };
export { saveObject, loadGame };

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

@ -5,6 +5,14 @@ import { OwnedAugmentationsOrderSetting, PurchaseAugmentationsOrderSetting } fro
* Represents the default settings the player could customize.
*/
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.
*/
@ -101,6 +109,8 @@ interface ISettings extends IDefaultSettings {
}
const defaultSettings: IDefaultSettings = {
ActiveScriptsServerPageSize: 10,
ActiveScriptsScriptPageSize: 10,
AutosaveInterval: 60,
CodeInstructionRunTime: 50,
DisableASCIIArt: false,
@ -123,6 +133,8 @@ const defaultSettings: IDefaultSettings = {
*/
// tslint:disable-next-line:variable-name
export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
ActiveScriptsServerPageSize: defaultSettings.ActiveScriptsServerPageSize,
ActiveScriptsScriptPageSize: defaultSettings.ActiveScriptsScriptPageSize,
AutosaveInterval: defaultSettings.AutosaveInterval,
CodeInstructionRunTime: 25,
DisableASCIIArt: defaultSettings.DisableASCIIArt,

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

@ -4,43 +4,42 @@ import { IMap } from "../types";
export const TerminalHelpText: string[] = [
"Type 'help name' to learn more about the command ",
"",
'alias [-g] [name="value"] Create or display Terminal aliases',
"analyze Get information about the current machine ",
"backdoor Install a backdoor on the current machine ",
"buy [-l/program] Purchase a program through the Dark Web",
"cat [file] Display a .msg, .lit, or .txt file",
"cd [dir] Change to a new directory",
"check [script] [args...] Print a script's logs to Terminal",
"clear Clear all text on the terminal ",
"cls See 'clear' command ",
"connect [ip/hostname] Connects to a remote server",
"download [script/text file] Downloads scripts or text files to your computer",
"expr [math expression] Evaluate a mathematical expression",
"free Check the machine's memory (RAM) usage",
"hack Hack the current machine",
"help [command] Display this help text, or the help text for a command",
"home Connect to home computer",
"hostname Displays the hostname of the machine",
"ifconfig Displays the IP address of the machine",
"kill [script/pid] [args...] Stops the specified script on the current server ",
"killall Stops all running scripts on the current machine",
"ls [dir] [| grep pattern] Displays all files on the machine",
"lscpu Displays the number of CPU cores on the machine",
"mem [script] [-t] [n] Displays the amount of RAM required to run the script",
"mv [src] [dest] Move/rename a text or script file",
"nano [file] Text editor - Open up and edit a script or text file",
"ps Display all scripts that are currently running",
"rm [file] Delete a file from the server",
"run [name] [-t] [n] [args...] Execute a program or script",
"scan Prints all immediately-available network connections",
"scan-analyze [d] [-a] Prints info for all servers up to <i>d</i> nodes away",
"scp [file] [server] Copies a file to a destination server",
"sudov Shows whether you have root access on this computer",
"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",
"unalias [alias name] Deletes the specified alias",
"wget [url] [target file] Retrieves code/text from a web server",
'alias [-g] [name="value"] Create or display Terminal aliases',
"analyze Get information about the current machine ",
"backdoor Install a backdoor on the current machine ",
"buy [-l/program] Purchase a program through the Dark Web",
"cat [file] Display a .msg, .lit, or .txt file",
"cd [dir] Change to a new directory",
"check [script] [args...] Print a script's logs to Terminal",
"clear Clear all text on the terminal ",
"cls See 'clear' command ",
"connect [ip/hostname] Connects to a remote server",
"download [script/text file] Downloads scripts or text files to your computer",
"expr [math expression] Evaluate a mathematical expression",
"free Check the machine's memory (RAM) usage",
"hack Hack the current machine",
"help [command] Display this help text, or the help text for a command",
"home Connect to home computer",
"hostname Displays the hostname of the machine",
"ifconfig Displays the IP address of the machine",
"kill [script/pid] [args...] Stops the specified script on the current server ",
"killall Stops all running scripts on the current machine",
"ls [dir] [| grep pattern] Displays all files on the machine",
"lscpu Displays the number of CPU cores on the machine",
"mem [script] [-t] [n] Displays the amount of RAM required to run the script",
"mv [src] [dest] Move/rename a text or script file",
"nano [file] Text editor - Open up and edit a script or text file",
"ps Display all scripts that are currently running",
"rm [file] Delete a file from the server",
"run [name] [-t n] [--tail] [args...] Execute a program or script",
"scan Prints all immediately-available network connections",
"scan-analyze [d] [-a] Prints info for all servers up to <i>d</i> nodes away",
"scp [file] [server] Copies a file to a destination server",
"sudov Shows whether you have root access on this computer",
"tail [script] [args...] Displays dynamic logs for the specified script",
"top Displays all running scripts and their RAM usage",
"unalias [alias name] Deletes the specified alias",
"wget [url] [target file] Retrieves code/text from a web server",
];
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"],
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 ",
"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",
],
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",
" ",

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

@ -44,14 +44,12 @@ import { Reputation } from "./ui/React/Reputation";
import { dialogBoxCreate } from "../utils/DialogBox";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import "./Exploits/tampering";
import "./Exploits/unclickable";
import { startTampering } from "./Exploits/tampering";
import { startUnclickable } from "./Exploits/unclickable";
import React from "react";
const Engine = {
indexedDb: undefined,
// Time variables (milliseconds unix epoch time)
_lastUpdate: new Date().getTime(),
@ -196,7 +194,7 @@ const Engine = {
Engine.Counters.autoSaveCounter = Infinity;
} else {
Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5;
saveObject.saveGame(Engine.indexedDb);
saveObject.saveGame();
}
}
@ -248,6 +246,8 @@ const Engine = {
},
load: function (saveString) {
startTampering();
startUnclickable();
// Load game from save or create new game
if (loadGame(saveString)) {
initBitNodeMultipliers(Player);
@ -420,48 +420,4 @@ const Engine = {
},
};
function load(cb) {
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 };
export { Engine };

@ -1,7 +1,7 @@
import React from "react";
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 "./engineStyle";
@ -11,3 +11,14 @@ ReactDOM.render(
</Theme>,
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 { Money } from "../React/Money";
import { MoneyRate } from "../React/MoneyRate";
import { use } from "../Context";
@ -40,18 +41,13 @@ export function ScriptProduction(): React.ReactElement {
<TableBody>
<TableRow>
<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 align="left" classes={{ root: classes.cell }}>
<Typography variant="body2">
<MoneyRate money={player.scriptProdSinceLastAug} />
<Money money={player.scriptProdSinceLastAug} />
</Typography>
</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 }}>
<Typography variant="body2">
(<MoneyRate money={prodRateSinceLastAug} />)

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

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

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

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

@ -4,56 +4,13 @@ import Typography from "@mui/material/Typography";
import Grid from "@mui/material/Grid";
import { Terminal } from "../Terminal";
import { Engine } from "../engine";
import { load } from "../db";
import { Player } from "../Player";
import { Engine } from "../engine";
import { GameRoot } from "./GameRoot";
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 {
const [show, setShow] = useState(false);
const [loaded, setLoaded] = useState(false);
@ -66,9 +23,19 @@ export function LoadingScreen(): React.ReactElement {
});
useEffect(() => {
load(() => {
setLoaded(true);
});
async function doLoad() {
await load()
.then((saveString) => {
Engine.load(saveString);
setLoaded(true);
})
.catch((reason) => {
console.error(reason);
Engine.load("");
setLoaded(true);
});
}
doLoad();
}, []);
if (loaded) {

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

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

@ -28,6 +28,7 @@ import { dialogBoxCreate } from "../../../utils/DialogBox";
import { ConfirmationModal } from "./ConfirmationModal";
import { Settings } from "../../Settings/Settings";
import { save, deleteGame } from "../../db";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@ -42,9 +43,7 @@ const useStyles = makeStyles((theme: Theme) =>
interface IProps {
player: IPlayer;
save: () => void;
delete: () => void;
export: () => void;
import: (evt: any) => void;
forceKill: () => void;
softReset: () => void;
}
@ -153,20 +152,38 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
Settings.Locale = event.target.value as string;
}
function importSave(): void {
if (window.File && window.FileReader && window.FileList && window.Blob) {
// var fileSelector = clearEventListeners("import-game-file-selector");
// fileSelector.addEventListener("change", openImportFileHandler, false);
const ii = importInput.current;
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 startImport(): void {
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;
const ii = importInput.current;
if (ii === null) throw new Error("import input should not be null");
ii.click();
}
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 (
@ -490,7 +507,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
</Button>
</Tooltip>
<Tooltip title={<Typography>import</Typography>}>
<Button onClick={importSave}>
<Button onClick={startImport}>
<UploadIcon color="primary" />
Import
<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>
</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">
<Typography>Changelog</Typography>
</Link>
@ -554,8 +574,10 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
<FileDiagnosticModal open={diagnosticOpen} onClose={() => setDiagnosticOpen(false)} />
<ConfirmationModal
onConfirm={() => {
props.delete();
setDeleteOpen(false);
deleteGame()
.then(() => location.reload())
.catch((r) => console.error(`Could not delete game: ${r}`));
}}
open={deleteGameOpen}
onClose={() => setDeleteOpen(false)}

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

@ -1,12 +1,31 @@
import React from "react";
import { createTheme, ThemeProvider, Theme, StyledEngineProvider } from "@mui/material/styles";
declare module "@mui/styles/defaultTheme" {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface DefaultTheme extends Theme {}
declare module "@mui/material/styles" {
interface 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 const colors = {
export let colors = {
primarylight: "#0f0",
primary: "#0c0",
primarydark: "#090",
@ -38,227 +57,242 @@ export const colors = {
combat: "#faffdf",
cha: "#a671d1",
int: "#6495ed",
rep: "#faffdf",
};
export const theme = createTheme({
palette: {
primary: {
light: colors.primarylight,
main: colors.primary,
dark: colors.primarydark,
let theme: Theme;
function refreshTheme() {
theme = createTheme({
colors: {
hp: "#dd3434",
money: "#ffd700",
hack: "#adff2f",
combat: "#faffdf",
cha: "#a671d1",
int: "#6495ed",
rep: "#faffdf",
},
secondary: {
light: colors.secondarylight,
main: colors.secondary,
dark: colors.secondarydark,
palette: {
primary: {
light: colors.primarylight,
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: {
light: colors.errorlight,
main: colors.error,
dark: colors.errordark,
typography: {
fontFamily: "monospace",
button: {
textTransform: "none",
},
},
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,
},
},
typography: {
fontFamily: "monospace",
button: {
textTransform: "none",
},
},
components: {
MuiInputBase: {
styleOverrides: {
root: {
backgroundColor: colors.well,
color: colors.primary,
components: {
MuiInputBase: {
styleOverrides: {
root: {
backgroundColor: colors.well,
color: colors.primary,
},
input: {
"&::placeholder": {
userSelect: "none",
color: colors.primarydark,
},
},
},
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",
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: {
styleOverrides: {
root: {
backgroundColor: colors.well,
borderBottomColor: "#fff",
},
underline: {
"&:hover": {
borderBottomColor: colors.primarydark,
},
"&:before": {
borderBottomColor: colors.primary,
},
"&:after": {
borderBottomColor: colors.primarylight,
borderRadius: 0,
},
},
},
},
MuiInputLabel: {
styleOverrides: {
root: {
color: colors.primarydark, // why is this switched?
userSelect: "none",
"&:before": {
color: colors.primarylight,
MuiSelect: {
styleOverrides: {
icon: {
color: colors.primary,
},
},
},
},
MuiButton: {
styleOverrides: {
root: {
backgroundColor: "#333",
border: "1px solid " + colors.well,
// color: colors.primary,
margin: "5px",
padding: "3px 5px",
"&:hover": {
MuiMenu: {
styleOverrides: {
list: {
backgroundColor: colors.well,
},
},
},
MuiMenuItem: {
styleOverrides: {
root: {
color: colors.primary,
},
},
},
MuiAccordionSummary: {
styleOverrides: {
root: {
backgroundColor: "#111",
},
},
},
MuiAccordionDetails: {
styleOverrides: {
root: {
backgroundColor: colors.black,
},
borderRadius: 0,
},
},
},
MuiSelect: {
styleOverrides: {
icon: {
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",
MuiIconButton: {
styleOverrides: {
root: {
color: colors.primary,
},
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: {
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,
},
},
},
},
});
});
console.log("refreshed");
}
refreshTheme();
interface IProps {
children: JSX.Element[] | JSX.Element;

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

@ -11,10 +11,6 @@
* This formula ensures that the effects of the statistic that is being processed
* has diminishing returns, but never loses its effectiveness as you continue
* 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 {
if (expFac <= 0 || expFac >= 1) {
@ -26,20 +22,3 @@ export function calculateEffectWithFactors(n: number, expFac: number, 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 UnusedWebpackPlugin = require("unused-webpack-plugin");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const DeadCodePlugin = require("webpack-deadcode-plugin");
module.exports = (env, argv) => {
const isDevServer = (env || {}).devServer === true;
@ -130,6 +131,10 @@ module.exports = (env, argv) => {
module: true,
}),
isDevelopment && new ReactRefreshWebpackPlugin(),
new DeadCodePlugin({
patterns: ["src/**/*.(js|jsx|css|ts|tsx)"],
exclude: ["**/*.(stories|spec).(js|jsx)"],
}),
].filter(Boolean),
target: "web",
entry: entry,