mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-27 01:53:48 +01:00
1576 lines
62 KiB
JavaScript
1576 lines
62 KiB
JavaScript
/**
|
|
* Game engine. Handles the main game loop as well as the main UI pages
|
|
*
|
|
* TODO: Separate UI functionality into its own component
|
|
*/
|
|
import {
|
|
convertTimeMsToTimeElapsedString,
|
|
replaceAt
|
|
} from "../utils/StringHelperFunctions";
|
|
import { logBoxUpdateText, logBoxOpened } from "../utils/LogBox";
|
|
import { Augmentations } from "./Augmentation/Augmentations";
|
|
import {
|
|
initAugmentations,
|
|
displayAugmentationsContent,
|
|
} from "./Augmentation/AugmentationHelpers";
|
|
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
|
|
import {
|
|
initBitNodeMultipliers
|
|
} from "./BitNode/BitNode";
|
|
import { Bladeburner } from "./Bladeburner";
|
|
import { CharacterOverviewComponent } from "./ui/React/CharacterOverview";
|
|
import { cinematicTextFlag } from "./CinematicText";
|
|
import { generateRandomContract } from "./CodingContractGenerator";
|
|
import { initCompanies } from "./Company/Companies";
|
|
import { Corporation } from "./Corporation/Corporation";
|
|
import { CONSTANTS } from "./Constants";
|
|
import { createDevMenu, closeDevMenu } from "./DevMenu";
|
|
import { Factions, initFactions } from "./Faction/Factions";
|
|
import {
|
|
displayFactionContent,
|
|
joinFaction,
|
|
processPassiveFactionRepGain,
|
|
inviteToFaction
|
|
} from "./Faction/FactionHelpers";
|
|
import { FconfSettings } from "./Fconf/FconfSettings";
|
|
import {
|
|
hasHacknetServers,
|
|
renderHacknetNodesUI,
|
|
clearHacknetNodesUI,
|
|
processHacknetEarnings
|
|
} from "./Hacknet/HacknetHelpers";
|
|
import { iTutorialStart } from "./InteractiveTutorial";
|
|
import { initLiterature } from "./Literature";
|
|
import { LocationName } from "./Locations/data/LocationNames";
|
|
import { LocationRoot } from "./Locations/ui/Root";
|
|
import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers";
|
|
import { inMission, currMission } from "./Missions";
|
|
import { workerScripts } from "./Netscript/WorkerScripts";
|
|
import {
|
|
loadAllRunningScripts,
|
|
updateOnlineScriptTimes,
|
|
} from "./NetscriptWorker";
|
|
import { Player } from "./Player";
|
|
import { prestigeAugmentation } from "./Prestige";
|
|
import {
|
|
displayCreateProgramContent,
|
|
getNumAvailableCreateProgram,
|
|
initCreateProgramButtons
|
|
} from "./Programs/ProgramHelpers";
|
|
import { redPillFlag } from "./RedPill";
|
|
import { saveObject, loadGame } from "./SaveObject";
|
|
import {
|
|
getCurrentEditor,
|
|
scriptEditorInit,
|
|
updateScriptEditorContent
|
|
} from "./Script/ScriptHelpers";
|
|
import { initForeignServers } from "./Server/AllServers";
|
|
import { Settings } from "./Settings/Settings";
|
|
import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags";
|
|
import { initSpecialServerIps } from "./Server/SpecialServerIps";
|
|
import {
|
|
initSymbolToStockMap,
|
|
stockMarketCycle,
|
|
processStockPrices,
|
|
displayStockMarketContent
|
|
} from "./StockMarket/StockMarket";
|
|
import { Terminal, postNetburnerText } from "./Terminal";
|
|
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
|
|
import {
|
|
clearSleevesPage,
|
|
createSleevesPage,
|
|
updateSleevesPage
|
|
} from "./PersonObjects/Sleeve/SleeveUI";
|
|
import {
|
|
clearResleevesPage,
|
|
createResleevesPage
|
|
} from "./PersonObjects/Resleeving/ResleevingUI";
|
|
|
|
import { createStatusText } from "./ui/createStatusText";
|
|
import { displayCharacterInfo } from "./ui/displayCharacterInfo";
|
|
import { Page, routing } from "./ui/navigationTracking";
|
|
import { numeralWrapper } from "./ui/numeralFormat";
|
|
import { setSettingsLabels } from "./ui/setSettingsLabels";
|
|
|
|
import { ActiveScriptsRoot } from "./ui/ActiveScripts/Root";
|
|
import { initializeMainMenuHeaders } from "./ui/MainMenu/Headers";
|
|
import { initializeMainMenuLinks, MainMenuLinks } from "./ui/MainMenu/Links";
|
|
|
|
import { dialogBoxCreate } from "../utils/DialogBox";
|
|
import { gameOptionsBoxClose, gameOptionsBoxOpen } from "../utils/GameOptions";
|
|
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
|
|
import { createElement } from "../utils/uiHelpers/createElement";
|
|
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
|
|
import { removeLoadingScreen } from "../utils/uiHelpers/removeLoadingScreen";
|
|
import { KEY } from "../utils/helpers/keyCodes";
|
|
|
|
import React from "react";
|
|
import ReactDOM from "react-dom";
|
|
|
|
/**
|
|
* Shortcuts to navigate through the game
|
|
* Alt-t - Terminal
|
|
* Alt-c - Character
|
|
* Alt-e - Script editor
|
|
* Alt-s - Active scripts
|
|
* Alt-h - Hacknet Nodes
|
|
* Alt-w - City
|
|
* Alt-j - Job
|
|
* Alt-r - Travel Agency of current city
|
|
* Alt-p - Create program
|
|
* Alt-f - Factions
|
|
* Alt-a - Augmentations
|
|
* Alt-u - Tutorial
|
|
* Alt-o - Options
|
|
*/
|
|
$(document).keydown(function(e) {
|
|
if (Settings.DisableHotkeys === true) {return;}
|
|
|
|
// These hotkeys should be disabled if the player is writing scripts
|
|
try {
|
|
if (getCurrentEditor().isFocused()) {
|
|
return;
|
|
}
|
|
} catch(e) {}
|
|
|
|
if (!Player.isWorking && !redPillFlag && !inMission && !cinematicTextFlag) {
|
|
if (e.keyCode == 84 && e.altKey) {
|
|
e.preventDefault();
|
|
Engine.loadTerminalContent();
|
|
} else if (e.keyCode === KEY.C && e.altKey) {
|
|
e.preventDefault();
|
|
Engine.loadCharacterContent();
|
|
} else if (e.keyCode === KEY.E && e.altKey) {
|
|
e.preventDefault();
|
|
Engine.loadScriptEditorContent();
|
|
} else if (e.keyCode === KEY.S && e.altKey) {
|
|
e.preventDefault();
|
|
Engine.loadActiveScriptsContent();
|
|
} else if (e.keyCode === KEY.H && e.altKey) {
|
|
e.preventDefault();
|
|
Engine.loadHacknetNodesContent();
|
|
} else if (e.keyCode === KEY.W && e.altKey) {
|
|
e.preventDefault();
|
|
Engine.loadLocationContent();
|
|
} else if (e.keyCode === KEY.J && e.altKey) {
|
|
e.preventDefault();
|
|
Engine.loadJobContent();
|
|
} else if (e.keyCode === KEY.R && e.altKey) {
|
|
e.preventDefault();
|
|
Engine.loadTravelContent();
|
|
} else if (e.keyCode === KEY.P && e.altKey) {
|
|
e.preventDefault();
|
|
Engine.loadCreateProgramContent();
|
|
} else if (e.keyCode === KEY.F && e.altKey) {
|
|
// Overriden by Fconf
|
|
if (routing.isOn(Page.Terminal) && FconfSettings.ENABLE_BASH_HOTKEYS) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
Engine.loadFactionsContent();
|
|
} else if (e.keyCode === KEY.A && e.altKey) {
|
|
e.preventDefault();
|
|
Engine.loadAugmentationsContent();
|
|
} else if (e.keyCode === KEY.U && e.altKey) {
|
|
e.preventDefault();
|
|
Engine.loadTutorialContent();
|
|
}
|
|
}
|
|
|
|
if (e.keyCode === KEY.O && e.altKey) {
|
|
e.preventDefault();
|
|
gameOptionsBoxOpen();
|
|
}
|
|
});
|
|
|
|
const Engine = {
|
|
version: "",
|
|
Debug: true,
|
|
|
|
// Clickable objects
|
|
Clickables: {
|
|
// Main menu buttons
|
|
saveMainMenuButton: null,
|
|
deleteMainMenuButton: null,
|
|
},
|
|
|
|
// Display objects
|
|
// TODO-Refactor this into its own component
|
|
Display: {
|
|
// Progress bar
|
|
progress: null,
|
|
|
|
// Display for status text (such as "Saved" or "Loaded")
|
|
statusText: null,
|
|
|
|
hacking_skill: null,
|
|
|
|
// Main menu content
|
|
terminalContent: null,
|
|
characterContent: null,
|
|
scriptEditorContent: null,
|
|
activeScriptsContent: null,
|
|
hacknetNodesContent: null,
|
|
createProgramContent: null,
|
|
factionsContent: null,
|
|
factionContent: null,
|
|
augmentationsContent: null,
|
|
tutorialContent: null,
|
|
infiltrationContent: null,
|
|
stockMarketContent: null,
|
|
locationContent: null,
|
|
workInProgressContent: null,
|
|
redPillContent: null,
|
|
cinematicTextContent: null,
|
|
missionContent: null,
|
|
|
|
// Character info
|
|
characterInfo: null,
|
|
},
|
|
|
|
// Time variables (milliseconds unix epoch time)
|
|
_lastUpdate: new Date().getTime(),
|
|
_idleSpeed: 200, // Speed (in ms) at which the main loop is updated
|
|
|
|
loadTerminalContent: function() {
|
|
Engine.hideAllContent();
|
|
Engine.Display.terminalContent.style.display = "block";
|
|
routing.navigateTo(Page.Terminal);
|
|
MainMenuLinks.Terminal.classList.add("active");
|
|
},
|
|
|
|
loadCharacterContent: function() {
|
|
Engine.hideAllContent();
|
|
Engine.Display.characterContent.style.display = "block";
|
|
Engine.updateCharacterInfo();
|
|
routing.navigateTo(Page.CharacterInfo);
|
|
MainMenuLinks.Stats.classList.add("active");
|
|
},
|
|
|
|
loadScriptEditorContent: function(filename = "", code = "") {
|
|
Engine.hideAllContent();
|
|
Engine.Display.scriptEditorContent.style.display = "block";
|
|
try {
|
|
getCurrentEditor().openScript(filename, code);
|
|
} catch(e) {
|
|
exceptionAlert(e);
|
|
}
|
|
|
|
updateScriptEditorContent();
|
|
routing.navigateTo(Page.ScriptEditor);
|
|
MainMenuLinks.ScriptEditor.classList.add("active");
|
|
},
|
|
|
|
loadActiveScriptsContent: function() {
|
|
Engine.hideAllContent();
|
|
Engine.Display.activeScriptsContent.style.display = "block";
|
|
routing.navigateTo(Page.ActiveScripts);
|
|
ReactDOM.render(
|
|
<ActiveScriptsRoot p={Player} workerScripts={workerScripts} />,
|
|
Engine.Display.activeScriptsContent
|
|
)
|
|
MainMenuLinks.ActiveScripts.classList.add("active");
|
|
},
|
|
|
|
loadHacknetNodesContent: function() {
|
|
Engine.hideAllContent();
|
|
Engine.Display.hacknetNodesContent.style.display = "block";
|
|
routing.navigateTo(Page.HacknetNodes);
|
|
renderHacknetNodesUI();
|
|
MainMenuLinks.HacknetNodes.classList.add("active");
|
|
},
|
|
|
|
loadCreateProgramContent: function() {
|
|
Engine.hideAllContent();
|
|
Engine.Display.createProgramContent.style.display = "block";
|
|
displayCreateProgramContent();
|
|
routing.navigateTo(Page.CreateProgram);
|
|
MainMenuLinks.CreateProgram.classList.add("active");
|
|
},
|
|
|
|
loadFactionsContent: function() {
|
|
Engine.hideAllContent();
|
|
Engine.Display.factionsContent.style.display = "block";
|
|
Engine.displayFactionsInfo();
|
|
routing.navigateTo(Page.Factions);
|
|
MainMenuLinks.Factions.classList.add("active");
|
|
},
|
|
|
|
loadFactionContent: function() {
|
|
Engine.hideAllContent();
|
|
Engine.Display.factionContent.style.display = "block";
|
|
routing.navigateTo(Page.Faction);
|
|
},
|
|
|
|
loadAugmentationsContent: function() {
|
|
Engine.hideAllContent();
|
|
Engine.Display.augmentationsContent.style.display = "block";
|
|
routing.navigateTo(Page.Augmentations);
|
|
displayAugmentationsContent(Engine.Display.augmentationsContent);
|
|
MainMenuLinks.Augmentations.classList.add("active");
|
|
},
|
|
|
|
loadTutorialContent: function() {
|
|
Engine.hideAllContent();
|
|
Engine.Display.tutorialContent.style.display = "block";
|
|
routing.navigateTo(Page.Tutorial);
|
|
MainMenuLinks.Tutorial.classList.add("active");
|
|
},
|
|
|
|
loadDevMenuContent: function() {
|
|
Engine.hideAllContent();
|
|
createDevMenu();
|
|
routing.navigateTo(Page.DevMenu);
|
|
MainMenuLinks.DevMenu.classList.add("active");
|
|
},
|
|
|
|
loadLocationContent: function(initiallyInCity=true) {
|
|
Engine.hideAllContent();
|
|
Engine.Display.locationContent.style.display = "block";
|
|
MainMenuLinks.City.classList.add("active");
|
|
|
|
routing.navigateTo(Page.Location);
|
|
const rootComponent = <LocationRoot
|
|
initiallyInCity={initiallyInCity}
|
|
engine={Engine}
|
|
p={Player}
|
|
/>
|
|
ReactDOM.render(rootComponent, Engine.Display.locationContent);
|
|
},
|
|
|
|
loadTravelContent: function() {
|
|
// Same as loadLocationContent() except first set the location to the travel agency,
|
|
// and make sure that the 'City' main menu link doesnt become 'active'
|
|
Engine.hideAllContent();
|
|
Player.gotoLocation(LocationName.TravelAgency);
|
|
Engine.Display.locationContent.style.display = "block";
|
|
MainMenuLinks.Travel.classList.add("active");
|
|
|
|
routing.navigateTo(Page.Location);
|
|
const rootComponent = <LocationRoot
|
|
initiallyInCity={false}
|
|
engine={Engine}
|
|
p={Player}
|
|
/>
|
|
ReactDOM.render(rootComponent, Engine.Display.locationContent);
|
|
},
|
|
|
|
loadJobContent: function() {
|
|
// Same as loadLocationContent(), except first set the location to the job.
|
|
// Make sure that the 'City' main menu link doesnt become 'active'
|
|
if (Player.companyName == "") {
|
|
dialogBoxCreate("You do not currently have a job! You can visit various companies " +
|
|
"in the city and try to find a job.");
|
|
return;
|
|
}
|
|
Engine.hideAllContent();
|
|
Player.gotoLocation(Player.companyName);
|
|
Engine.Display.locationContent.style.display = "block";
|
|
MainMenuLinks.Job.classList.add("active");
|
|
|
|
routing.navigateTo(Page.Location);
|
|
const rootComponent = <LocationRoot
|
|
initiallyInCity={false}
|
|
engine={Engine}
|
|
p={Player}
|
|
/>
|
|
ReactDOM.render(rootComponent, Engine.Display.locationContent);
|
|
},
|
|
|
|
loadWorkInProgressContent: function() {
|
|
Engine.hideAllContent();
|
|
var mainMenu = document.getElementById("mainmenu-container");
|
|
mainMenu.style.visibility = "hidden";
|
|
Engine.Display.workInProgressContent.style.display = "block";
|
|
routing.navigateTo(Page.WorkInProgress);
|
|
},
|
|
|
|
loadRedPillContent: function() {
|
|
Engine.hideAllContent();
|
|
var mainMenu = document.getElementById("mainmenu-container");
|
|
mainMenu.style.visibility = "hidden";
|
|
Engine.Display.redPillContent.style.display = "block";
|
|
routing.navigateTo(Page.RedPill);
|
|
},
|
|
|
|
loadCinematicTextContent: function() {
|
|
Engine.hideAllContent();
|
|
var mainMenu = document.getElementById("mainmenu-container");
|
|
mainMenu.style.visibility = "hidden";
|
|
Engine.Display.cinematicTextContent.style.display = "block";
|
|
routing.navigateTo(Page.CinematicText);
|
|
},
|
|
|
|
loadInfiltrationContent: function() {
|
|
Engine.hideAllContent();
|
|
Engine.Display.infiltrationContent.style.display = "block";
|
|
routing.navigateTo(Page.Infiltration);
|
|
},
|
|
|
|
loadStockMarketContent: function() {
|
|
Engine.hideAllContent();
|
|
Engine.Display.stockMarketContent.style.display = "block";
|
|
routing.navigateTo(Page.StockMarket);
|
|
displayStockMarketContent();
|
|
},
|
|
|
|
loadGangContent: function() {
|
|
Engine.hideAllContent();
|
|
if (document.getElementById("gang-container") || Player.inGang()) {
|
|
Player.gang.displayGangContent(Player);
|
|
routing.navigateTo(Page.Gang);
|
|
} else {
|
|
Engine.loadTerminalContent();
|
|
routing.navigateTo(Page.Terminal);
|
|
}
|
|
},
|
|
|
|
loadMissionContent: function() {
|
|
Engine.hideAllContent();
|
|
document.getElementById("mainmenu-container").style.visibility = "hidden";
|
|
document.getElementById("character-overview-wrapper").style.visibility = "hidden";
|
|
Engine.Display.missionContent.style.display = "block";
|
|
routing.navigateTo(Page.Mission);
|
|
},
|
|
|
|
loadCorporationContent: function() {
|
|
if (Player.corporation instanceof Corporation) {
|
|
Engine.hideAllContent();
|
|
document.getElementById("character-overview-wrapper").style.visibility = "hidden";
|
|
routing.navigateTo(Page.Corporation);
|
|
Player.corporation.createUI();
|
|
}
|
|
},
|
|
|
|
loadBladeburnerContent: function() {
|
|
if (Player.bladeburner instanceof Bladeburner) {
|
|
try {
|
|
Engine.hideAllContent();
|
|
routing.navigateTo(Page.Bladeburner);
|
|
Player.bladeburner.createContent();
|
|
} catch(e) {
|
|
exceptionAlert(e);
|
|
}
|
|
}
|
|
},
|
|
|
|
loadSleevesContent: function() {
|
|
// This is for Duplicate Sleeves page, not Re-sleeving @ Vita Life
|
|
try {
|
|
Engine.hideAllContent();
|
|
routing.navigateTo(Page.Sleeves);
|
|
createSleevesPage(Player);
|
|
} catch(e) {
|
|
exceptionAlert(e);
|
|
}
|
|
},
|
|
|
|
loadResleevingContent: function() {
|
|
try {
|
|
Engine.hideAllContent();
|
|
routing.navigateTo(Page.Resleeves);
|
|
createResleevesPage(Player);
|
|
} catch(e) {
|
|
exceptionAlert(e);
|
|
}
|
|
},
|
|
|
|
// Helper function that hides all content
|
|
hideAllContent: function() {
|
|
Engine.Display.terminalContent.style.display = "none";
|
|
Engine.Display.characterContent.style.display = "none";
|
|
Engine.Display.scriptEditorContent.style.display = "none";
|
|
|
|
Engine.Display.activeScriptsContent.style.display = "none";
|
|
ReactDOM.unmountComponentAtNode(Engine.Display.activeScriptsContent);
|
|
|
|
clearHacknetNodesUI();
|
|
Engine.Display.createProgramContent.style.display = "none";
|
|
|
|
Engine.Display.factionsContent.style.display = "none";
|
|
|
|
Engine.Display.factionContent.style.display = "none";
|
|
ReactDOM.unmountComponentAtNode(Engine.Display.factionContent);
|
|
|
|
Engine.Display.augmentationsContent.style.display = "none";
|
|
ReactDOM.unmountComponentAtNode(Engine.Display.augmentationsContent);
|
|
|
|
Engine.Display.tutorialContent.style.display = "none";
|
|
|
|
Engine.Display.locationContent.style.display = "none";
|
|
ReactDOM.unmountComponentAtNode(Engine.Display.locationContent);
|
|
|
|
Engine.Display.workInProgressContent.style.display = "none";
|
|
Engine.Display.redPillContent.style.display = "none";
|
|
Engine.Display.cinematicTextContent.style.display = "none";
|
|
Engine.Display.infiltrationContent.style.display = "none";
|
|
Engine.Display.stockMarketContent.style.display = "none";
|
|
Engine.Display.missionContent.style.display = "none";
|
|
if (document.getElementById("gang-container")) {
|
|
document.getElementById("gang-container").style.display = "none";
|
|
}
|
|
|
|
if (Player.inGang()) {
|
|
Player.gang.clearUI();
|
|
}
|
|
if (Player.corporation instanceof Corporation) {
|
|
Player.corporation.clearUI();
|
|
}
|
|
|
|
if (Player.bladeburner instanceof Bladeburner) {
|
|
Player.bladeburner.clearContent();
|
|
}
|
|
|
|
clearResleevesPage();
|
|
clearSleevesPage();
|
|
|
|
// Make nav menu tabs inactive
|
|
Engine.inactivateMainMenuLinks();
|
|
|
|
// Close dev menu
|
|
closeDevMenu();
|
|
},
|
|
|
|
// Remove 'active' css class from all main menu links
|
|
inactivateMainMenuLinks: function() {
|
|
MainMenuLinks.Terminal.classList.remove("active");
|
|
MainMenuLinks.ScriptEditor.classList.remove("active");
|
|
MainMenuLinks.ActiveScripts.classList.remove("active");
|
|
MainMenuLinks.CreateProgram.classList.remove("active");
|
|
MainMenuLinks.Stats.classList.remove("active");
|
|
MainMenuLinks.Factions.classList.remove("active");
|
|
MainMenuLinks.Augmentations.classList.remove("active");
|
|
MainMenuLinks.HacknetNodes.classList.remove("active");
|
|
MainMenuLinks.Sleeves.classList.remove("active");
|
|
MainMenuLinks.City.classList.remove("active");
|
|
MainMenuLinks.Travel.classList.remove("active");
|
|
MainMenuLinks.Job.classList.remove("active");
|
|
MainMenuLinks.StockMarket.classList.remove("active");
|
|
MainMenuLinks.Bladeburner.classList.remove("active");
|
|
MainMenuLinks.Corporation.classList.remove("active");
|
|
MainMenuLinks.Gang.classList.remove("active");
|
|
MainMenuLinks.Tutorial.classList.remove("active");
|
|
MainMenuLinks.Options.classList.remove("active");
|
|
MainMenuLinks.DevMenu.classList.remove("active");
|
|
},
|
|
|
|
displayCharacterOverviewInfo: function() {
|
|
ReactDOM.render(<CharacterOverviewComponent />, document.getElementById('character-overview-text'));
|
|
|
|
const save = document.getElementById("character-overview-save-button");
|
|
const flashClass = "flashing-button";
|
|
if(!Settings.AutosaveInterval) {
|
|
save.classList.add(flashClass);
|
|
} else {
|
|
save.classList.remove(flashClass);
|
|
}
|
|
},
|
|
|
|
/// Display character info
|
|
updateCharacterInfo: function() {
|
|
displayCharacterInfo(Engine.Display.characterInfo, Player);
|
|
},
|
|
|
|
// TODO Refactor this into Faction implementation
|
|
displayFactionsInfo: function() {
|
|
removeChildrenFromElement(Engine.Display.factionsContent);
|
|
|
|
// Factions
|
|
Engine.Display.factionsContent.appendChild(createElement("h1", {
|
|
innerText:"Factions"
|
|
}));
|
|
Engine.Display.factionsContent.appendChild(createElement("p", {
|
|
innerText:"Lists all factions you have joined"
|
|
}));
|
|
var factionsList = createElement("ul");
|
|
Engine.Display.factionsContent.appendChild(createElement("br"));
|
|
|
|
// Add a button for each faction you are a member of
|
|
for (var i = 0; i < Player.factions.length; ++i) {
|
|
(function () {
|
|
var factionName = Player.factions[i];
|
|
|
|
factionsList.appendChild(createElement("a", {
|
|
class:"a-link-button", innerText:factionName, padding:"4px", margin:"4px",
|
|
display:"inline-block",
|
|
clickListener:()=>{
|
|
Engine.loadFactionContent();
|
|
displayFactionContent(factionName);
|
|
return false;
|
|
}
|
|
}));
|
|
factionsList.appendChild(createElement("br"));
|
|
}()); // Immediate invocation
|
|
}
|
|
Engine.Display.factionsContent.appendChild(factionsList);
|
|
Engine.Display.factionsContent.appendChild(createElement("br"));
|
|
|
|
// Invited Factions
|
|
Engine.Display.factionsContent.appendChild(createElement("h1", {
|
|
innerText:"Outstanding Faction Invitations"
|
|
}));
|
|
Engine.Display.factionsContent.appendChild(createElement("p", {
|
|
width:"70%",
|
|
innerText:"Lists factions you have been invited to, as well as " +
|
|
"factions you have previously rejected. You can accept " +
|
|
"these faction invitations at any time."
|
|
}));
|
|
var invitationsList = createElement("ul");
|
|
|
|
// Add a button to accept for each faction you have invitiations for
|
|
for (var i = 0; i < Player.factionInvitations.length; ++i) {
|
|
(function () {
|
|
var factionName = Player.factionInvitations[i];
|
|
|
|
var item = createElement("li", {padding:"6px", margin:"6px"});
|
|
item.appendChild(createElement("p", {
|
|
innerText:factionName, display:"inline", margin:"4px", padding:"4px"
|
|
}));
|
|
item.appendChild(createElement("a", {
|
|
innerText:"Accept Faction Invitation",
|
|
class:"a-link-button", display:"inline", margin:"4px", padding:"4px",
|
|
clickListener: (e) => {
|
|
if (!e.isTrusted) { return false; }
|
|
joinFaction(Factions[factionName]);
|
|
for (var i = 0; i < Player.factionInvitations.length; ++i) {
|
|
if (Player.factionInvitations[i] == factionName || Factions[Player.factionInvitations[i]].isBanned) {
|
|
Player.factionInvitations.splice(i, 1);
|
|
i--;
|
|
}
|
|
}
|
|
Engine.displayFactionsInfo();
|
|
return false;
|
|
}
|
|
}));
|
|
|
|
invitationsList.appendChild(item);
|
|
}());
|
|
}
|
|
|
|
Engine.Display.factionsContent.appendChild(invitationsList);
|
|
},
|
|
|
|
// Main Game Loop
|
|
idleTimer: function() {
|
|
// Get time difference
|
|
var _thisUpdate = new Date().getTime();
|
|
var diff = _thisUpdate - Engine._lastUpdate;
|
|
var offset = diff % Engine._idleSpeed;
|
|
|
|
// Divide this by cycle time to determine how many cycles have elapsed since last update
|
|
diff = Math.floor(diff / Engine._idleSpeed);
|
|
|
|
if (diff > 0) {
|
|
// Update the game engine by the calculated number of cycles
|
|
Engine._lastUpdate = _thisUpdate - offset;
|
|
Player.lastUpdate = _thisUpdate - offset;
|
|
Engine.updateGame(diff);
|
|
}
|
|
|
|
window.requestAnimationFrame(Engine.idleTimer);
|
|
},
|
|
|
|
updateGame: function(numCycles = 1) {
|
|
var time = numCycles * Engine._idleSpeed;
|
|
if (Player.totalPlaytime == null) {Player.totalPlaytime = 0;}
|
|
if (Player.playtimeSinceLastAug == null) {Player.playtimeSinceLastAug = 0;}
|
|
if (Player.playtimeSinceLastBitnode == null) {Player.playtimeSinceLastBitnode = 0;}
|
|
Player.totalPlaytime += time;
|
|
Player.playtimeSinceLastAug += time;
|
|
Player.playtimeSinceLastBitnode += time;
|
|
|
|
// Start Manual hack
|
|
if (Terminal.actionStarted === true) {
|
|
Engine._totalActionTime = Terminal.actionTime;
|
|
Engine._actionTimeLeft = Terminal.actionTime;
|
|
Engine._actionInProgress = true;
|
|
Engine._actionProgressBarCount = 1;
|
|
Engine._actionProgressStr = "[ ]";
|
|
Engine._actionTimeStr = "Time left: ";
|
|
Terminal.actionStarted = false;
|
|
}
|
|
|
|
// Working
|
|
if (Player.isWorking) {
|
|
if (Player.workType == CONSTANTS.WorkTypeFaction) {
|
|
Player.workForFaction(numCycles);
|
|
} else if (Player.workType == CONSTANTS.WorkTypeCreateProgram) {
|
|
Player.createProgramWork(numCycles);
|
|
} else if (Player.workType == CONSTANTS.WorkTypeStudyClass) {
|
|
Player.takeClass(numCycles);
|
|
} else if (Player.workType == CONSTANTS.WorkTypeCrime) {
|
|
Player.commitCrime(numCycles);
|
|
} else if (Player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
|
|
Player.workPartTime(numCycles);
|
|
} else {
|
|
Player.work(numCycles);
|
|
}
|
|
}
|
|
|
|
// Update stock prices
|
|
if (Player.hasWseAccount) {
|
|
processStockPrices(numCycles);
|
|
}
|
|
|
|
// Gang, if applicable
|
|
if (Player.inGang()) {
|
|
Player.gang.process(numCycles, Player);
|
|
}
|
|
|
|
// Mission
|
|
if (inMission && currMission) {
|
|
currMission.process(numCycles);
|
|
}
|
|
|
|
// Corporation
|
|
if (Player.corporation instanceof Corporation) {
|
|
// Stores cycles in a "buffer". Processed separately using Engine Counters
|
|
Player.corporation.storeCycles(numCycles);
|
|
}
|
|
|
|
if (Player.bladeburner instanceof Bladeburner) {
|
|
Player.bladeburner.storeCycles(numCycles);
|
|
}
|
|
|
|
// Sleeves
|
|
for (let i = 0; i < Player.sleeves.length; ++i) {
|
|
if (Player.sleeves[i] instanceof Sleeve) {
|
|
const expForOtherSleeves = Player.sleeves[i].process(Player, numCycles);
|
|
|
|
// This sleeve earns experience for other sleeves
|
|
if (expForOtherSleeves == null) { continue; }
|
|
for (let j = 0; j < Player.sleeves.length; ++j) {
|
|
if (j === i) { continue; }
|
|
Player.sleeves[j].gainExperience(Player, expForOtherSleeves, numCycles, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Counters
|
|
Engine.decrementAllCounters(numCycles);
|
|
Engine.checkCounters();
|
|
|
|
// Manual hacks
|
|
if (Engine._actionInProgress == true) {
|
|
Engine.updateHackProgress(numCycles);
|
|
}
|
|
|
|
// Update the running time of all active scripts
|
|
updateOnlineScriptTimes(numCycles);
|
|
|
|
// Hacknet Nodes
|
|
processHacknetEarnings(numCycles);
|
|
},
|
|
|
|
/**
|
|
* Counters for the main event loop. Represent the number of game cycles that
|
|
* are required for something to happen. These counters are in game cycles,
|
|
* which is once every 200ms
|
|
*/
|
|
Counters: {
|
|
autoSaveCounter: 300,
|
|
updateSkillLevelsCounter: 10,
|
|
updateDisplays: 3,
|
|
updateDisplaysMed: 9,
|
|
updateDisplaysLong: 15,
|
|
updateActiveScriptsDisplay: 5,
|
|
createProgramNotifications: 10,
|
|
checkFactionInvitations: 100,
|
|
passiveFactionGrowth: 600,
|
|
messages: 150,
|
|
sCr: 1500,
|
|
mechanicProcess: 5, // Processes certain mechanics (Corporation, Bladeburner)
|
|
contractGeneration: 3000, // Generate Coding Contracts
|
|
},
|
|
|
|
decrementAllCounters: function(numCycles = 1) {
|
|
for (var counter in Engine.Counters) {
|
|
if (Engine.Counters.hasOwnProperty(counter)) {
|
|
Engine.Counters[counter] = Engine.Counters[counter] - numCycles;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Checks if any counters are 0. If they are, executes whatever
|
|
* is necessary and then resets the counter
|
|
*/
|
|
checkCounters: function() {
|
|
if (Engine.Counters.autoSaveCounter <= 0) {
|
|
if (Settings.AutosaveInterval == null) {
|
|
Settings.AutosaveInterval = 60;
|
|
}
|
|
if (Settings.AutosaveInterval === 0) {
|
|
Engine.Counters.autoSaveCounter = Infinity;
|
|
} else {
|
|
Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5;
|
|
saveObject.saveGame(indexedDb);
|
|
}
|
|
}
|
|
|
|
if (Engine.Counters.updateSkillLevelsCounter <= 0) {
|
|
Player.updateSkillLevels();
|
|
Engine.Counters.updateSkillLevelsCounter = 10;
|
|
}
|
|
|
|
if (Engine.Counters.updateActiveScriptsDisplay <= 0) {
|
|
if (routing.isOn(Page.ActiveScripts)) {
|
|
ReactDOM.render(
|
|
<ActiveScriptsRoot p={Player} workerScripts={workerScripts} />,
|
|
Engine.Display.activeScriptsContent
|
|
)
|
|
}
|
|
|
|
Engine.Counters.updateActiveScriptsDisplay = 5;
|
|
}
|
|
|
|
if (Engine.Counters.updateDisplays <= 0) {
|
|
Engine.displayCharacterOverviewInfo();
|
|
if (routing.isOn(Page.HacknetNodes)) {
|
|
renderHacknetNodesUI();
|
|
} else if (routing.isOn(Page.CreateProgram)) {
|
|
displayCreateProgramContent();
|
|
} else if (routing.isOn(Page.Sleeves)) {
|
|
updateSleevesPage();
|
|
}
|
|
|
|
if (logBoxOpened) {
|
|
logBoxUpdateText();
|
|
}
|
|
|
|
Engine.Counters.updateDisplays = 3;
|
|
}
|
|
|
|
if (Engine.Counters.updateDisplaysMed <= 0) {
|
|
if (routing.isOn(Page.CharacterInfo)) {
|
|
Engine.updateCharacterInfo();
|
|
}
|
|
Engine.Counters.updateDisplaysMed = 9;
|
|
}
|
|
|
|
if (Engine.Counters.updateDisplaysLong <= 0) {
|
|
if (routing.isOn(Page.Gang) && Player.inGang()) {
|
|
Player.gang.updateGangContent();
|
|
} else if (routing.isOn(Page.ScriptEditor)) {
|
|
updateScriptEditorContent();
|
|
}
|
|
Engine.Counters.updateDisplaysLong = 15;
|
|
}
|
|
|
|
if (Engine.Counters.createProgramNotifications <= 0) {
|
|
var num = getNumAvailableCreateProgram();
|
|
var elem = document.getElementById("create-program-notification");
|
|
if (num > 0) {
|
|
elem.innerHTML = num;
|
|
elem.setAttribute("class", "notification-on");
|
|
} else {
|
|
elem.innerHTML = "";
|
|
elem.setAttribute("class", "notification-off");
|
|
}
|
|
Engine.Counters.createProgramNotifications = 10;
|
|
}
|
|
|
|
if (Engine.Counters.checkFactionInvitations <= 0) {
|
|
var invitedFactions = Player.checkForFactionInvitations();
|
|
if (invitedFactions.length > 0) {
|
|
if (Player.firstFacInvRecvd === false) {
|
|
Player.firstFacInvRecvd = true;
|
|
document.getElementById("factions-tab").style.display = "list-item";
|
|
document.getElementById("character-menu-header").click();
|
|
document.getElementById("character-menu-header").click();
|
|
}
|
|
|
|
var randFaction = invitedFactions[Math.floor(Math.random() * invitedFactions.length)];
|
|
inviteToFaction(randFaction);
|
|
}
|
|
Engine.Counters.checkFactionInvitations = 100;
|
|
}
|
|
|
|
if (Engine.Counters.passiveFactionGrowth <= 0) {
|
|
var adjustedCycles = Math.floor((600 - Engine.Counters.passiveFactionGrowth));
|
|
processPassiveFactionRepGain(adjustedCycles);
|
|
Engine.Counters.passiveFactionGrowth = 600;
|
|
}
|
|
|
|
if (Engine.Counters.messages <= 0) {
|
|
checkForMessagesToSend();
|
|
if (Augmentations[AugmentationNames.TheRedPill].owned) {
|
|
Engine.Counters.messages = 4500; // 15 minutes for Red pill message
|
|
} else {
|
|
Engine.Counters.messages = 150;
|
|
}
|
|
}
|
|
|
|
if (Engine.Counters.sCr <= 0) {
|
|
if (Player.hasWseAccount) {
|
|
stockMarketCycle();
|
|
}
|
|
Engine.Counters.sCr = 1500;
|
|
}
|
|
|
|
if (Engine.Counters.mechanicProcess <= 0) {
|
|
if (Player.corporation instanceof Corporation) {
|
|
Player.corporation.process();
|
|
}
|
|
if (Player.bladeburner instanceof Bladeburner) {
|
|
try {
|
|
Player.bladeburner.process();
|
|
} catch(e) {
|
|
exceptionAlert("Exception caught in Bladeburner.process(): " + e);
|
|
}
|
|
|
|
}
|
|
Engine.Counters.mechanicProcess = 5;
|
|
}
|
|
|
|
if (Engine.Counters.contractGeneration <= 0) {
|
|
// X% chance of a contract being generated
|
|
if (Math.random() <= 0.25) {
|
|
generateRandomContract();
|
|
}
|
|
Engine.Counters.contractGeneration = 3000;
|
|
}
|
|
},
|
|
|
|
// Calculates the hack progress for a manual (non-scripted) hack and updates the progress bar/time accordingly
|
|
// TODO Refactor this into Terminal module
|
|
_totalActionTime: 0,
|
|
_actionTimeLeft: 0,
|
|
_actionTimeStr: "Time left: ",
|
|
_actionProgressStr: "[ ]",
|
|
_actionProgressBarCount: 1,
|
|
_actionInProgress: false,
|
|
updateHackProgress: function(numCycles = 1) {
|
|
var timeElapsedMilli = numCycles * Engine._idleSpeed;
|
|
Engine._actionTimeLeft -= (timeElapsedMilli/ 1000); // Substract idle speed (ms)
|
|
Engine._actionTimeLeft = Math.max(Engine._actionTimeLeft, 0);
|
|
|
|
// Calculate percent filled
|
|
var percent = Math.round((1 - Engine._actionTimeLeft / Engine._totalActionTime) * 100);
|
|
|
|
// Update progress bar
|
|
while (Engine._actionProgressBarCount * 2 <= percent) {
|
|
Engine._actionProgressStr = replaceAt(Engine._actionProgressStr, Engine._actionProgressBarCount, "|");
|
|
Engine._actionProgressBarCount += 1;
|
|
}
|
|
|
|
// Update hack time remaining
|
|
Engine._actionTimeStr = "Time left: " + Math.max(0, Math.round(Engine._actionTimeLeft)).toString() + "s";
|
|
document.getElementById("hack-progress").innerHTML = Engine._actionTimeStr;
|
|
|
|
// Dynamically update progress bar
|
|
document.getElementById("hack-progress-bar").innerHTML = Engine._actionProgressStr.replace( / /g, " " );
|
|
|
|
// Once percent is 100, the hack is completed
|
|
if (percent >= 100) {
|
|
Engine._actionInProgress = false;
|
|
Terminal.finishAction();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Collapses a main menu header. Used when initializing the game.
|
|
* @param elems {HTMLElement[]} Elements under header
|
|
*/
|
|
closeMainMenuHeader: function(elems) {
|
|
for (var i = 0; i < elems.length; ++i) {
|
|
elems[i].style.maxHeight = null;
|
|
elems[i].style.opacity = 0;
|
|
elems[i].style.pointerEvents = "none";
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Expands a main menu header. Used when initializing the game.
|
|
* @param elems {HTMLElement[]} Elements under header
|
|
*/
|
|
openMainMenuHeader: function(elems) {
|
|
for (var i = 0; i < elems.length; ++i) {
|
|
elems[i].style.maxHeight = elems[i].scrollHeight + "px";
|
|
elems[i].style.display = "block";
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Used in game when clicking on a main menu header (NOT used for initialization)
|
|
* @param open {boolean} Whether header is being opened or closed
|
|
* @param elems {HTMLElement[]} li Elements under header
|
|
* @param links {HTMLElement[]} a elements under header
|
|
*/
|
|
toggleMainMenuHeader: function(open, elems, links) {
|
|
for (var i = 0; i < elems.length; ++i) {
|
|
if (open) {
|
|
elems[i].style.opacity = 1;
|
|
elems[i].style.maxHeight = elems[i].scrollHeight + "px";
|
|
} else {
|
|
elems[i].style.opacity = 0;
|
|
elems[i].style.maxHeight = null;
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < links.length; ++i) {
|
|
if (open) {
|
|
links[i].style.opacity = 1;
|
|
links[i].style.maxHeight = links[i].scrollHeight + "px";
|
|
links[i].style.pointerEvents = "auto";
|
|
} else {
|
|
links[i].style.opacity = 0;
|
|
links[i].style.maxHeight = null;
|
|
links[i].style.pointerEvents = "none";
|
|
}
|
|
}
|
|
},
|
|
|
|
load: function(saveString) {
|
|
// Initialize main menu accordion panels to all start as "open"
|
|
const terminal = document.getElementById("terminal-tab");
|
|
const createScript = document.getElementById("create-script-tab");
|
|
const activeScripts = document.getElementById("active-scripts-tab");
|
|
const createProgram = document.getElementById("create-program-tab");
|
|
const stats = document.getElementById("stats-tab");
|
|
const factions = document.getElementById("factions-tab");
|
|
const augmentations = document.getElementById("augmentations-tab");
|
|
const hacknetnodes = document.getElementById("hacknet-nodes-tab");
|
|
const city = document.getElementById("city-tab");
|
|
const travel = document.getElementById("travel-tab");
|
|
const job = document.getElementById("job-tab");
|
|
const stockmarket = document.getElementById("stock-market-tab");
|
|
const bladeburner = document.getElementById("bladeburner-tab");
|
|
const corp = document.getElementById("corporation-tab");
|
|
const gang = document.getElementById("gang-tab");
|
|
const tutorial = document.getElementById("tutorial-tab");
|
|
const options = document.getElementById("options-tab");
|
|
const dev = document.getElementById("dev-tab");
|
|
|
|
// Load game from save or create new game
|
|
if (loadGame(saveString)) {
|
|
initBitNodeMultipliers(Player);
|
|
Engine.setDisplayElements(); // Sets variables for important DOM elements
|
|
Engine.init(); // Initialize buttons, work, etc.
|
|
initAugmentations(); // Also calls Player.reapplyAllAugmentations()
|
|
Player.reapplyAllSourceFiles();
|
|
if (Player.hasWseAccount) {
|
|
initSymbolToStockMap();
|
|
}
|
|
initLiterature();
|
|
updateSourceFileFlags(Player);
|
|
|
|
// Calculate the number of cycles have elapsed while offline
|
|
Engine._lastUpdate = new Date().getTime();
|
|
var lastUpdate = Player.lastUpdate;
|
|
var numCyclesOffline = Math.floor((Engine._lastUpdate - lastUpdate) / Engine._idleSpeed);
|
|
|
|
// Process offline progress
|
|
var offlineProductionFromScripts = loadAllRunningScripts(); // This also takes care of offline production for those scripts
|
|
if (Player.isWorking) {
|
|
console.log("work() called in load() for " + numCyclesOffline * Engine._idleSpeed + " milliseconds");
|
|
if (Player.workType == CONSTANTS.WorkTypeFaction) {
|
|
Player.workForFaction(numCyclesOffline);
|
|
} else if (Player.workType == CONSTANTS.WorkTypeCreateProgram) {
|
|
Player.createProgramWork(numCyclesOffline);
|
|
} else if (Player.workType == CONSTANTS.WorkTypeStudyClass) {
|
|
Player.takeClass(numCyclesOffline);
|
|
} else if (Player.workType == CONSTANTS.WorkTypeCrime) {
|
|
Player.commitCrime(numCyclesOffline);
|
|
} else if (Player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
|
|
Player.workPartTime(numCyclesOffline);
|
|
} else {
|
|
Player.work(numCyclesOffline);
|
|
}
|
|
}
|
|
|
|
// Hacknet Nodes offline progress
|
|
var offlineProductionFromHacknetNodes = processHacknetEarnings(numCyclesOffline);
|
|
const hacknetProdInfo = hasHacknetServers() ?
|
|
`${numeralWrapper.format(offlineProductionFromHacknetNodes, "0.000a")} hashes` :
|
|
`${numeralWrapper.formatMoney(offlineProductionFromHacknetNodes)}`;
|
|
|
|
// Passive faction rep gain offline
|
|
processPassiveFactionRepGain(numCyclesOffline);
|
|
|
|
// Stock Market offline progress
|
|
if (Player.hasWseAccount) {
|
|
processStockPrices(numCyclesOffline);
|
|
}
|
|
|
|
// Gang progress for BitNode 2
|
|
if (Player.inGang()) {
|
|
Player.gang.process(numCyclesOffline, Player);
|
|
}
|
|
|
|
// Corporation offline progress
|
|
if (Player.corporation instanceof Corporation) {
|
|
Player.corporation.storeCycles(numCyclesOffline);
|
|
}
|
|
|
|
// Bladeburner offline progress
|
|
if (Player.bladeburner instanceof Bladeburner) {
|
|
Player.bladeburner.storeCycles(numCyclesOffline);
|
|
}
|
|
|
|
// Sleeves offline progress
|
|
for (let i = 0; i < Player.sleeves.length; ++i) {
|
|
if (Player.sleeves[i] instanceof Sleeve) {
|
|
const expForOtherSleeves = Player.sleeves[i].process(Player, numCyclesOffline);
|
|
|
|
// This sleeve earns experience for other sleeves
|
|
if (expForOtherSleeves == null) { continue; }
|
|
for (let j = 0; j < Player.sleeves.length; ++j) {
|
|
if (j === i) { continue; }
|
|
Player.sleeves[j].gainExperience(Player, expForOtherSleeves, numCyclesOffline, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update total playtime
|
|
var time = numCyclesOffline * Engine._idleSpeed;
|
|
if (Player.totalPlaytime == null) {Player.totalPlaytime = 0;}
|
|
if (Player.playtimeSinceLastAug == null) {Player.playtimeSinceLastAug = 0;}
|
|
if (Player.playtimeSinceLastBitnode == null) {Player.playtimeSinceLastBitnode = 0;}
|
|
Player.totalPlaytime += time;
|
|
Player.playtimeSinceLastAug += time;
|
|
Player.playtimeSinceLastBitnode += time;
|
|
|
|
Player.lastUpdate = Engine._lastUpdate;
|
|
Engine.start(); // Run main game loop and Scripts loop
|
|
removeLoadingScreen();
|
|
const timeOfflineString = convertTimeMsToTimeElapsedString(time);
|
|
dialogBoxCreate(`Offline for ${timeOfflineString}. While you were offline, your scripts ` +
|
|
"generated <span class='money-gold'>" +
|
|
numeralWrapper.formatMoney(offlineProductionFromScripts) + "</span> " +
|
|
"and your Hacknet Nodes generated <span class='money-gold'>" + hacknetProdInfo + "</span>");
|
|
// Close main menu accordions for loaded game
|
|
var visibleMenuTabs = [terminal, createScript, activeScripts, stats,
|
|
hacknetnodes, city, tutorial, options, dev];
|
|
if (Player.firstFacInvRecvd) {visibleMenuTabs.push(factions);}
|
|
else {factions.style.display = "none";}
|
|
if (Player.firstAugPurchased) {visibleMenuTabs.push(augmentations);}
|
|
else {augmentations.style.display = "none";}
|
|
if (Player.companyName !== "") {visibleMenuTabs.push(job);}
|
|
else {job.style.display = "none";}
|
|
if (Player.firstTimeTraveled) {visibleMenuTabs.push(travel);}
|
|
else {travel.style.display = "none";}
|
|
if (Player.firstProgramAvailable) {visibleMenuTabs.push(createProgram);}
|
|
else {createProgram.style.display = "none";}
|
|
if (Player.hasWseAccount) {visibleMenuTabs.push(stockmarket);}
|
|
else {stockmarket.style.display = "none";}
|
|
if(Player.bladeburner instanceof Bladeburner) {visibleMenuTabs.push(bladeburner);}
|
|
else {bladeburner.style.display = "none";}
|
|
if(Player.corporation instanceof Corporation) {visibleMenuTabs.push(corp);}
|
|
else {corp.style.display = "none";}
|
|
if(Player.inGang()) {visibleMenuTabs.push(gang);}
|
|
else {gang.style.display = "none";}
|
|
|
|
Engine.closeMainMenuHeader(visibleMenuTabs);
|
|
} else {
|
|
// No save found, start new game
|
|
console.log("Initializing new game");
|
|
initBitNodeMultipliers(Player);
|
|
initSpecialServerIps();
|
|
Engine.setDisplayElements(); // Sets variables for important DOM elements
|
|
Engine.start(); // Run main game loop and Scripts loop
|
|
Player.init();
|
|
initForeignServers(Player.getHomeComputer());
|
|
initCompanies();
|
|
initFactions();
|
|
initAugmentations();
|
|
initMessages();
|
|
initLiterature();
|
|
updateSourceFileFlags(Player);
|
|
|
|
// Open main menu accordions for new game
|
|
const hackingHdr = document.getElementById("hacking-menu-header");
|
|
hackingHdr.classList.toggle("opened");
|
|
const characterHdr = document.getElementById("character-menu-header");
|
|
characterHdr.classList.toggle("opened");
|
|
const worldHdr = document.getElementById("world-menu-header");
|
|
worldHdr.classList.toggle("opened");
|
|
const helpHdr = document.getElementById("help-menu-header");
|
|
helpHdr.classList.toggle("opened");
|
|
|
|
// Hide tabs that wont be revealed until later
|
|
factions.style.display = "none";
|
|
augmentations.style.display = "none";
|
|
job.style.display = "none";
|
|
stockmarket.style.display = "none";
|
|
travel.style.display = "none";
|
|
createProgram.style.display = "none";
|
|
bladeburner.style.display = "none";
|
|
corp.style.display = "none";
|
|
gang.style.display = "none";
|
|
dev.style.display = "none";
|
|
|
|
Engine.openMainMenuHeader(
|
|
[terminal, createScript, activeScripts, stats,
|
|
hacknetnodes, city,
|
|
tutorial, options]
|
|
);
|
|
|
|
// Start interactive tutorial
|
|
iTutorialStart();
|
|
removeLoadingScreen();
|
|
}
|
|
// Initialize labels on game settings
|
|
setSettingsLabels();
|
|
scriptEditorInit();
|
|
Terminal.resetTerminalInput();
|
|
},
|
|
|
|
setDisplayElements: function() {
|
|
// Content elements
|
|
Engine.Display.terminalContent = document.getElementById("terminal-container");
|
|
routing.navigateTo(Page.Terminal);
|
|
|
|
Engine.Display.characterContent = document.getElementById("character-container");
|
|
Engine.Display.characterContent.style.display = "none";
|
|
|
|
Engine.Display.scriptEditorContent = document.getElementById("script-editor-container");
|
|
Engine.Display.scriptEditorContent.style.display = "none";
|
|
|
|
Engine.Display.activeScriptsContent = document.getElementById("active-scripts-container");
|
|
Engine.Display.activeScriptsContent.style.display = "none";
|
|
|
|
Engine.Display.hacknetNodesContent = document.getElementById("hacknet-nodes-container");
|
|
Engine.Display.hacknetNodesContent.style.display = "none";
|
|
|
|
Engine.Display.createProgramContent = document.getElementById("create-program-container");
|
|
Engine.Display.createProgramContent.style.display = "none";
|
|
|
|
Engine.Display.factionsContent = document.getElementById("factions-container");
|
|
Engine.Display.factionsContent.style.display = "none";
|
|
|
|
Engine.Display.factionContent = document.getElementById("faction-container");
|
|
Engine.Display.factionContent.style.display = "none";
|
|
|
|
Engine.Display.augmentationsContent = document.getElementById("augmentations-container");
|
|
Engine.Display.augmentationsContent.style.display = "none";
|
|
|
|
|
|
Engine.Display.tutorialContent = document.getElementById("tutorial-container");
|
|
Engine.Display.tutorialContent.style.display = "none";
|
|
|
|
Engine.Display.infiltrationContent = document.getElementById("infiltration-container");
|
|
Engine.Display.infiltrationContent.style.display = "none";
|
|
|
|
Engine.Display.stockMarketContent = document.getElementById("stock-market-container");
|
|
Engine.Display.stockMarketContent.style.display = "none";
|
|
|
|
Engine.Display.missionContent = document.getElementById("mission-container");
|
|
Engine.Display.missionContent.style.display = "none";
|
|
|
|
// Character info
|
|
Engine.Display.characterInfo = document.getElementById("character-content");
|
|
|
|
// Location page (page that shows up when you visit a specific location in World)
|
|
Engine.Display.locationContent = document.getElementById("location-container");
|
|
Engine.Display.locationContent.style.display = "none";
|
|
|
|
// Work In Progress
|
|
Engine.Display.workInProgressContent = document.getElementById("work-in-progress-container");
|
|
Engine.Display.workInProgressContent.style.display = "none";
|
|
|
|
// Red Pill / Hack World Daemon
|
|
Engine.Display.redPillContent = document.getElementById("red-pill-container");
|
|
Engine.Display.redPillContent.style.display = "none";
|
|
|
|
// Cinematic Text
|
|
Engine.Display.cinematicTextContent = document.getElementById("cinematic-text-container");
|
|
Engine.Display.cinematicTextContent.style.display = "none";
|
|
|
|
// Initialize references to main menu links
|
|
if (!initializeMainMenuLinks()) {
|
|
const errorMsg = "Failed to initialize Main Menu Links. Please try refreshing the page. " +
|
|
"If that doesn't work, report the issue to the developer";
|
|
exceptionAlert(new Error(errorMsg));
|
|
console.error(errorMsg);
|
|
return;
|
|
}
|
|
},
|
|
|
|
// Initialization
|
|
init: function() {
|
|
// Import game link
|
|
document.getElementById("import-game-link").onclick = function() {
|
|
saveObject.importGame();
|
|
};
|
|
|
|
// Initialize Main Menu Headers (this must be done after initializing the links)
|
|
if (!initializeMainMenuHeaders(Player, process.env.NODE_ENV === "development")) {
|
|
const errorMsg = "Failed to initialize Main Menu Headers. Please try refreshing the page. " +
|
|
"If that doesn't work, report the issue to the developer";
|
|
exceptionAlert(new Error(errorMsg));
|
|
console.error(errorMsg);
|
|
return;
|
|
}
|
|
|
|
MainMenuLinks.Terminal.addEventListener("click", function() {
|
|
Engine.loadTerminalContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.ScriptEditor.addEventListener("click", function() {
|
|
Engine.loadScriptEditorContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.ActiveScripts.addEventListener("click", function() {
|
|
Engine.loadActiveScriptsContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.CreateProgram.addEventListener("click", function() {
|
|
Engine.loadCreateProgramContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.Stats.addEventListener("click", function() {
|
|
Engine.loadCharacterContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.Factions.addEventListener("click", function() {
|
|
Engine.loadFactionsContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.Augmentations.addEventListener("click", function() {
|
|
Engine.loadAugmentationsContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.HacknetNodes.addEventListener("click", function() {
|
|
Engine.loadHacknetNodesContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.Sleeves.addEventListener("click", function() {
|
|
Engine.loadSleevesContent();
|
|
MainMenuLinks.Sleeves.classList.add("active");
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.City.addEventListener("click", function() {
|
|
Engine.loadLocationContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.Travel.addEventListener("click", function() {
|
|
Engine.loadTravelContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.Job.addEventListener("click", function() {
|
|
Engine.loadJobContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.StockMarket.addEventListener("click", function() {
|
|
Engine.loadStockMarketContent();
|
|
MainMenuLinks.StockMarket.classList.add("active");
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.Bladeburner.addEventListener("click", function() {
|
|
Engine.loadBladeburnerContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.Corporation.addEventListener("click", function() {
|
|
Engine.loadCorporationContent();
|
|
MainMenuLinks.Corporation.classList.add("active");
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.Gang.addEventListener("click", function() {
|
|
Engine.loadGangContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.Tutorial.addEventListener("click", function() {
|
|
Engine.loadTutorialContent();
|
|
return false;
|
|
});
|
|
|
|
MainMenuLinks.DevMenu.addEventListener("click", function() {
|
|
if (process.env.NODE_ENV === "development") {
|
|
Engine.loadDevMenuContent();
|
|
}
|
|
return false;
|
|
});
|
|
|
|
// Active scripts list
|
|
Engine.ActiveScriptsList = document.getElementById("active-scripts-list");
|
|
|
|
// Save, Delete, Import/Export buttons
|
|
Engine.Clickables.saveMainMenuButton = document.getElementById("save-game-link");
|
|
Engine.Clickables.saveMainMenuButton.addEventListener("click", function() {
|
|
saveObject.saveGame(indexedDb);
|
|
return false;
|
|
});
|
|
|
|
Engine.Clickables.deleteMainMenuButton = document.getElementById("delete-game-link");
|
|
Engine.Clickables.deleteMainMenuButton.addEventListener("click", function() {
|
|
saveObject.deleteGame(indexedDb);
|
|
return false;
|
|
});
|
|
|
|
document.getElementById("export-game-link").addEventListener("click", function() {
|
|
saveObject.exportGame();
|
|
return false;
|
|
});
|
|
|
|
// Character Overview buttons
|
|
document.getElementById("character-overview-save-button").addEventListener("click", function() {
|
|
saveObject.saveGame(indexedDb);
|
|
return false;
|
|
});
|
|
|
|
document.getElementById("character-overview-options-button").addEventListener("click", function() {
|
|
gameOptionsBoxOpen();
|
|
return false;
|
|
});
|
|
|
|
// Create Program buttons
|
|
initCreateProgramButtons();
|
|
|
|
// Message at the top of terminal
|
|
postNetburnerText();
|
|
|
|
// Player was working cancel button
|
|
if (Player.isWorking) {
|
|
var cancelButton = document.getElementById("work-in-progress-cancel-button");
|
|
cancelButton.addEventListener("click", function() {
|
|
if (Player.workType == CONSTANTS.WorkTypeFaction) {
|
|
var fac = Factions[Player.currentWorkFactionName];
|
|
Player.finishFactionWork(true);
|
|
} else if (Player.workType == CONSTANTS.WorkTypeCreateProgram) {
|
|
Player.finishCreateProgramWork(true);
|
|
} else if (Player.workType == CONSTANTS.WorkTypeStudyClass) {
|
|
Player.finishClass();
|
|
} else if (Player.workType == CONSTANTS.WorkTypeCrime) {
|
|
Player.finishCrime(true);
|
|
} else if (Player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
|
|
Player.finishWorkPartTime();
|
|
} else {
|
|
Player.finishWork(true);
|
|
}
|
|
});
|
|
Engine.loadWorkInProgressContent();
|
|
}
|
|
|
|
// Character overview screen
|
|
document.getElementById("character-overview-container").style.display = "block";
|
|
|
|
// Remove classes from links (they might be set from tutorial)
|
|
document.getElementById("terminal-menu-link").removeAttribute("class");
|
|
document.getElementById("stats-menu-link").removeAttribute("class");
|
|
document.getElementById("create-script-menu-link").removeAttribute("class");
|
|
document.getElementById("active-scripts-menu-link").removeAttribute("class");
|
|
document.getElementById("hacknet-nodes-menu-link").removeAttribute("class");
|
|
document.getElementById("city-menu-link").removeAttribute("class");
|
|
document.getElementById("tutorial-menu-link").removeAttribute("class");
|
|
|
|
// Copy Save Data to Clipboard
|
|
document.getElementById("copy-save-to-clipboard-link").addEventListener("click", function() {
|
|
const saveString = saveObject.getSaveString();
|
|
if (!navigator.clipboard) {
|
|
// Async Clipboard API not supported, so we'll use this using the
|
|
// textarea and document.execCommand('copy') trick
|
|
const textArea = document.createElement("textarea");
|
|
textArea.value = saveString;
|
|
textArea.setAttribute("readonly", '');
|
|
textArea.style.position = 'absolute';
|
|
textArea.left = '-9999px';
|
|
document.body.appendChild(textArea);
|
|
textArea.focus();
|
|
textArea.select();
|
|
try {
|
|
const successful = document.execCommand("copy");
|
|
if (successful) {
|
|
createStatusText("Copied save to clipboard");
|
|
} else {
|
|
createStatusText("Failed to copy save");
|
|
}
|
|
} catch(e) {
|
|
console.error("Unable to copy save data to clipboard using document.execCommand('copy')");
|
|
createStatusText("Failed to copy save");
|
|
}
|
|
document.body.removeChild(textArea);
|
|
} else {
|
|
// Use the Async Clipboard API
|
|
navigator.clipboard.writeText(saveString).then(function() {
|
|
createStatusText("Copied save to clipboard");
|
|
}, function(e) {
|
|
console.error("Unable to copy save data to clipboard using Async API");
|
|
createStatusText("Failed to copy save");
|
|
})
|
|
}
|
|
});
|
|
|
|
// DEBUG Delete active Scripts on home
|
|
document.getElementById("debug-delete-scripts-link").addEventListener("click", function() {
|
|
console.log("Deleting running scripts on home computer");
|
|
Player.getHomeComputer().runningScripts = [];
|
|
dialogBoxCreate("Forcefully deleted all running scripts on home computer. Please save and refresh page");
|
|
gameOptionsBoxClose();
|
|
return false;
|
|
});
|
|
|
|
// DEBUG Soft Reset
|
|
document.getElementById("debug-soft-reset").addEventListener("click", function() {
|
|
dialogBoxCreate("Soft Reset!");
|
|
prestigeAugmentation();
|
|
gameOptionsBoxClose();
|
|
return false;
|
|
});
|
|
},
|
|
|
|
start: function() {
|
|
// Run main loop
|
|
Engine.idleTimer();
|
|
}
|
|
};
|
|
|
|
var indexedDb, indexedDbRequest;
|
|
window.onload = function() {
|
|
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.log("Error opening indexedDB: ");
|
|
console.log(e);
|
|
return Engine.load(null); // Try to load from localstorage
|
|
};
|
|
|
|
indexedDbRequest.onsuccess = function(e) {
|
|
console.log("Opening bitburnerSave database successful!");
|
|
indexedDb = e.target.result;
|
|
var transaction = indexedDb.transaction(["savestring"]);
|
|
var objectStore = transaction.objectStore("savestring");
|
|
var request = objectStore.get("save");
|
|
request.onerror = function(e) {
|
|
console.log("Error in Database request to get savestring: " + e);
|
|
return Engine.load(null); // Try to load from localstorage
|
|
}
|
|
|
|
request.onsuccess = function(e) {
|
|
Engine.load(request.result);
|
|
}
|
|
};
|
|
|
|
indexedDbRequest.onupgradeneeded = function(e) {
|
|
var db = e.target.result;
|
|
var objectStore = db.createObjectStore("savestring");
|
|
}
|
|
};
|
|
|
|
export {Engine};
|