add difficulty to bitnode screen

This commit is contained in:
Olivier Gagnon 2021-09-21 16:49:38 -04:00
parent c3ac16f330
commit 57a5c8b0b4
13 changed files with 187 additions and 446 deletions

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

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

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

8
src/SaveObject.d.ts vendored

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

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

@ -61,7 +61,6 @@ import { scananalyze } from "./commands/scananalyze";
import { scp } from "./commands/scp"; import { scp } from "./commands/scp";
import { sudov } from "./commands/sudov"; import { sudov } from "./commands/sudov";
import { tail } from "./commands/tail"; import { tail } from "./commands/tail";
import { theme } from "./commands/theme";
import { top } from "./commands/top"; import { top } from "./commands/top";
import { unalias } from "./commands/unalias"; import { unalias } from "./commands/unalias";
import { wget } from "./commands/wget"; import { wget } from "./commands/wget";
@ -683,7 +682,6 @@ export class Terminal implements ITerminal {
scp: scp, scp: scp,
sudov: sudov, sudov: sudov,
tail: tail, tail: tail,
theme: theme,
top: top, top: top,
unalias: unalias, unalias: unalias,
wget: wget, wget: wget,

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

86
src/db.tsx Normal file

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

2
src/engine.d.ts vendored

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

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

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

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

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