Added BitNode multipliers for purchased servers. Fixed bugs in new Script Editor implementation. Added documentation for script editors

This commit is contained in:
danielyxie 2019-01-29 22:02:27 -08:00
parent d54f0906f0
commit a09ea46a38
31 changed files with 3355 additions and 2558 deletions

@ -6,7 +6,7 @@
#codemirror-form-wrapper {
height: 80%;
margin: 10px 0px 10px 6px;
margin: 10px 0px 0px 6px;
}
.CodeMirror {
@ -29,7 +29,6 @@
background-color: #8F908A;
}
/**
* Show Invisibles
*/
@ -38,3 +37,13 @@
pointer-events: none;
color: #404F7D;
}
/**
* Vim command display
*/
#codemirror-vim-command-display-wrapper {
background-color: white;
font-size: 13px;
height: 30px;
margin-left: 6px;
}

@ -93,6 +93,7 @@
overflow: auto;
z-index: 1;
color: #fff;
max-height: 50%;
}
#script-editor-options-panel fieldset {

3284
dist/engine.bundle.js vendored

File diff suppressed because it is too large Load Diff

14
dist/engine.css vendored

@ -5,7 +5,7 @@
*/
#codemirror-form-wrapper {
height: 80%;
margin: 10px 0px 10px 6px; }
margin: 10px 0px 0px 6px; }
.CodeMirror {
height: 100%;
@ -32,6 +32,15 @@
pointer-events: none;
color: #404F7D; }
/**
* Vim command display
*/
#codemirror-vim-command-display-wrapper {
background-color: white;
font-size: 13px;
height: 30px;
margin-left: 6px; }
/* COLORS */
/* Attributes */
/* COLORS */
@ -886,7 +895,8 @@ button {
padding: 2px;
overflow: auto;
z-index: 1;
color: #fff; }
color: #fff;
max-height: 50%; }
#script-editor-options-panel fieldset {
margin-top: 8px;

1855
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

29
dist/vendor.css vendored

@ -5383,6 +5383,35 @@ THE SOFTWARE.
.cm-s-zenburn .CodeMirror-focused div.CodeMirror-selected {
background: #4f4f4f; }
.CodeMirror-dialog {
position: absolute;
left: 0;
right: 0;
background: inherit;
z-index: 15;
padding: .1em .8em;
overflow: hidden;
color: inherit; }
.CodeMirror-dialog-top {
border-bottom: 1px solid #eee;
top: 0; }
.CodeMirror-dialog-bottom {
border-top: 1px solid #eee;
bottom: 0; }
.CodeMirror-dialog input {
border: none;
outline: none;
background: transparent;
width: 20em;
color: inherit;
font-family: monospace; }
.CodeMirror-dialog button {
font-size: 70%; }
.CodeMirror-foldmarker {
color: blue;
text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;

@ -23,6 +23,7 @@ secrets that you've been searching for.
Basic Gameplay <basicgameplay>
Advanced Gameplay <advancedgameplay>
Keyboard Shortcuts <shortcuts>
Script Editors <scripteditors>
Game Frozen or Stuck? <gamefrozen>
Changelog <changelog>

@ -0,0 +1,140 @@
.. _scripteditors:
Script Editors
==============
Third-party libraries are used to implement the game's Script Editor(s). There are
currently two options for the Script Editor:
* `Ace <https://ace.c9.io/>`_
* `CodeMirror <https://codemirror.net/>`_
You can select which of the two editors you want to use on the Script Editor page
('Create Script' on the main menu).
Ace was the game's original Script Editor, while CodeMirror was added later in
v0.43.0. The two editors share many of the same features, so there is not a significant
difference between the two. Currently, CodeMirror is slightly more modern,
more customizable, and has a few quality-of-life improvements compared to Ace.
Universal Key Bindings
----------------------
These keyboard shortcuts are available in both the Ace and CodeMirror editors, regardless
of what key binding option you are using:
============= ===========================================================================
Shortcut Action
============= ===========================================================================
Ctrl + b Save script and return to :ref:`terminal`
Ctrl + space Show Autocomplete Hints
============= ===========================================================================
.. _scripteditor_linter:
Linter
------
Both script editors contain a linter, which is a tool that analyzes your
code and flags anything it thinks might be an error. You can see
warnings and errors from the linter on the left-hand side of the script editor. There
will be an icon on whatever lines the linter thinks might be problematic. Hovering
over the icon will display information on what the issue is.
Note that **just because the linter shows an error/warning, this does NOT automatically mean that**
**your script is broken and will fail to run.** This is especially true if you are using
:ref:`netscriptjs`. The linter used by the script editors isn't necessarily perfect or
up-to-date. Furthermore, the linter does not affect anything when you actually run scripts.
Ace
---
The following documents what the different settings/options do for the Ace editor,
as well as the different key binding settings. Note that the
information for the key bindings may not be completely comprehensive. You'll
have to dig into the editor source code if you want to learn more.
Settings
~~~~~~~~
===================== ===========================================================================================================
Setting Effect
===================== ===========================================================================================================
Theme Switch between different color schemes
Key Binding Switch between different key binding options. This changes what keyboard shortcuts are available
Highlight Active Line When enabled, the line on which the cursor currently resides will be highlighted.
Show Invisibles When enabled, you will be able to view hidden whitespace characters such as spaces, tabs, and newlines.
Use Soft Tab When enabled, tabs will be replaced with spaces
Max Error Count Specifies the (approximate) number of lines that will be linted
===================== ===========================================================================================================
Ace Key Bindings
~~~~~~~~~~~~~~~~
For Ace, the "Ace" Key Binding setting uses the default configuration. A list of these
`can be found here <https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts>`_.
Vim Key Bindings
~~~~~~~~~~~~~~~~
For Ace, the "Vim" Key Binding setting configures the editor to use
`Vim <https://en.wikipedia.org/wiki/Vim_(text_editor)>`_ key mappings. Note that while this tries
to emulate Vim features as faithfully as possible, it is not a complete Vim implementation.
Since I'm not familiar with Vim, I'll leave
`Ace's Vim Mode implementation here <https://github.com/ajaxorg/ace/blob/96088d0fc292daf0706b2d029cc03c3799be5974/lib/ace/keyboard/vim.js#L860>`_,
which I believe shows most of the implemented features.
Note that the following Vim Ex commands will properly save the script and/or quit the editor in game:
======= ==============================================
Command Effect
======= ==============================================
:w Save the script and return to :ref:`terminal`
:q Return to :ref:`terminal` **WITHOUT** saving
:x Save the script and return to :ref:`terminal`
:wq Save the script and return to :ref:`terminal`
======= ==============================================
Emacs Key Bindings
~~~~~~~~~~~~~~~~~~
For Ace, the "Emacs" Key Binding setting configures the editor to use
`Emacs <https://en.wikipedia.org/wiki/Emacs>`_ key mappings. Note that while this tries
to emulate the Emacs key mappings as faithfully as possible, it won't necessarily be a
complete implementation.
Since I'm not familiar with Emacs, I'll leave
`Ace's Emacs Mode implementation here <https://github.com/ajaxorg/ace/blob/96088d0fc292daf0706b2d029cc03c3799be5974/lib/ace/keyboard/emacs.js#L343>`_,
which I believe shows most of the implemented features.
CodeMirror
----------
The following documents what the different settings/options do for the CodeMirror editor,
as well as the shortcuts for the different key binding settings. Note that the
information for the key bindings may not be completely comprehensive. You'll
have to dig into the editor source code if you want to learn everything.
Settings
~~~~~~~~
========================== ===========================================================================================================
Setting Effect
========================== ===========================================================================================================
Theme Switch between different color schemes
Key Binding Switch between different key binding options. This changes what keyboard shortcuts are available
Highlight Active Line When enabled, the line on which the cursor currently resides will be highlighted.
Show Invisibles When enabled, you will be able to view hidden whitespace characters such as spaces, tabs, and newlines.
Use Soft Tab When enabled, tabs will be replaced with spaces
Auto-Close Brackets/Quotes When enabled, any opening brackets or quotes that are typed will be closed
Enable Linting Enable/Disable the :ref:`scripteditor_linter`
Continue Comments When enabled, pressing 'Enter' inside a comment block will continue the comment on the next line
========================== ===========================================================================================================
Default Key Bindings
~~~~~~~~~~~~~~~~~~~~
.. todo:: Fill out
Sublime Key Bindings
~~~~~~~~~~~~~~~~~~~~
.. todo:: Fill out
Vim Key Bindings
~~~~~~~~~~~~~~~~
.. todo:: Fill out
Emacs Key Bindings
~~~~~~~~~~~~~~~~~~
.. todo:: Fill out

@ -35,20 +35,7 @@ Alt + o Switch to 'Options' page
Script Editor
-------------
These shortcuts are available only in the Script Editor
============= ===========================================================================
Shortcut Action
============= ===========================================================================
Ctrl + b Save script and return to :ref:`terminal`
Ctrl + space Function autocompletion
============= ===========================================================================
In the Script Editor you can configure your key binding mode to three preset options:
* `Ace <https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts>`_
* Vim
* Emacs
See the :ref:`Script Editor <scripteditors>` documentation for more details.
Terminal Shortcuts
------------------
@ -92,10 +79,13 @@ Alt + f Move cursor to next word
Ctrl + h/d Delete previous character ('Backspace')
============= ===========================================================================
Misc Shortcuts
--------------
Popup/Dialog Box Shortcuts
--------------------------
The following shortcuts work if there are any popup or dialog boxes on the screen.
============= ===========================================================================
Shortcut Action
============= ===========================================================================
Esc Close a script's log window
Esc Close the current popup, cancelling any prompts on a dialog box
Enter Clicks the "Yes/Confirm" option for every dialog box
============= ===========================================================================

@ -120,6 +120,9 @@
<div id="ace-editor"></div>
<form id="codemirror-form-wrapper"><textarea id="codemirror-editor"></textarea></form>
<div id="codemirror-vim-command-display-wrapper">
Key Buffer: <span id="codemirror-vim-command-display"></span>
</div>
<div id="script-editor-buttons-wrapper"></div> <!-- Buttons below script editor -->
</div> <!-- End wrapper -->

@ -168,6 +168,7 @@ function initBitNodes() {
"In this BitNode:<br><br>" +
"Your stats are significantly decreased.<br>" +
"All methods of gaining money are half as profitable (except Stock Market)<br>" +
"Purchased servers are more expensive, have less max RAM, and a lower maximum limit<br>" +
"Augmentations are 5x as expensive and require twice as much reputation<br><br>" +
"Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. This Source-File unlocks Sleeve technology in other BitNodes. " +
@ -337,6 +338,9 @@ function initBitNodeMultipliers() {
BitNodeMultipliers.CorporationValuation = 0.5;
BitNodeMultipliers.AugmentationMoneyCost = 5;
BitNodeMultipliers.AugmentationRepCost = 2;
BitNodeMultipliers.PurchasedServerCost = 5;
BitNodeMultipliers.PurchasedServerLimit = 0.6;
BitNodeMultipliers.PurchasedServerMaxRam = 0.5;
case 11: //The Big Crash
BitNodeMultipliers.ServerMaxMoney = 0.1;
BitNodeMultipliers.ServerStartingMoney = 0.1;

@ -125,6 +125,20 @@ interface IBitNodeMultipliers {
*/
ManualHackMoney: number;
/**
* Influence how much it costs to purchase a server
*/
PurchasedServerCost: number;
/**
* Influences the maximum number of purchased servers you can have
*/
PurchasedServerLimit: number;
/**
* Influences the maximum allowed RAM for a purchased server
*/
PurchasedServerMaxRam: number;
/**
* Influences the minimum favor the player must have with a faction before they can donate to gain rep.
*/
@ -184,6 +198,10 @@ export const BitNodeMultipliers: IBitNodeMultipliers = {
ServerStartingSecurity: 1,
ServerWeakenRate: 1,
PurchasedServerCost: 1,
PurchasedServerLimit: 1,
PurchasedServerMaxRam: 1,
CompanyWorkMoney: 1,
CrimeMoney: 1,
HacknetNodeMoney: 1,

@ -524,10 +524,11 @@ export let CONSTANTS: IMap<any> = {
* Script Editor Changes:
** Added new script editor: CodeMirror. You can choose between the old editor (Ace) or CodeMirror
** Navigation keyboard shortcuts no longer work on the script editor page
** Navigation keyboard shortcuts no longer work if the script editor is focused
* Trying to programmatically run a script (run(), exec()) with a 'threads' argument of 0 will now cause the function to return false without running the script
* Home Computer RAM is now capped at 2 ^ 30 GB (1073741824 GB)
* The maximum amount, maximum RAM, and cost of purchasing servers can now vary between different BitNodes (new BitNode multipliers)
* Pop-up dialog boxes are a little bit bigger
* Bug Fix: When importing scripts, "./" will now be properly ignored (e.g. import { foo } from "./lib.script" )
`

@ -481,7 +481,7 @@ function createFactionAugmentationDisplayElements(augmentationsList, augs, facti
}
var item = createElement("li");
var span = createElement("span", {display:"inline-block"});
var span = createElement("span", { display:"inline-block", margin: "4px", padding: "4px" });
var aDiv = createElement("div", {tooltip:aug.info});
var aElem = createElement("a", {
innerText:aug.name, display:"inline",

@ -1,10 +1,12 @@
import {Engine} from "./engine";
import {Player} from "./Player";
import {Settings} from "./Settings/Settings";
import {clearEventListeners} from "../utils/uiHelpers/clearEventListeners";
import {createElement} from "../utils/uiHelpers/createElement";
import {createPopup} from "../utils/uiHelpers/createPopup";
import {removeElementById} from "../utils/uiHelpers/removeElementById";
import { Engine } from "./engine";
import { Player } from "./Player";
import { Settings } from "./Settings/Settings";
import { initializeMainMenuLinks } from "./ui/MainMenu/Links";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
//Ordered array of keys to Interactive Tutorial Steps
const orderedITutorialSteps = [
@ -472,7 +474,19 @@ function iTutorialEnd() {
}
console.log("Ending interactive tutorial");
// Initialize references to main menu links
// We have to call initializeMainMenuLinks() again because the Interactive Tutorial
// re-creates Main menu links with clearEventListeners()
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;
}
Engine.init();
ITutorial.currStep = iTutorialSteps.End;
ITutorial.isRunning = false;
document.getElementById("interactive-tutorial-container").style.display = "none";

@ -12,8 +12,9 @@ import {hasBladeburnerSF} from "./NetscriptFunctions";
import {Locations} from "./Locations";
import {Player} from "./Player";
import {Server, AllServers, AddToAllServers} from "./Server";
import {purchaseServer,
purchaseRamForHomeComputer} from "./ServerPurchases";
import { getPurchaseServerCost,
purchaseServer,
purchaseRamForHomeComputer} from "./ServerPurchases";
import {Settings} from "./Settings/Settings";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import {SpecialServerNames, SpecialServerIps} from "./SpecialServerIps";
@ -191,16 +192,16 @@ function displayLocationContent() {
purchaseHomeRam.style.display = "none";
purchaseHomeCores.style.display = "none";
purchase2gb.innerHTML = "Purchase 2GB Server - $" + formatNumber(2*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase4gb.innerHTML = "Purchase 4GB Server - $" + formatNumber(4*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase8gb.innerHTML = "Purchase 8GB Server - $" + formatNumber(8*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase16gb.innerHTML = "Purchase 16GB Server - $" + formatNumber(16*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase32gb.innerHTML = "Purchase 32GB Server - $" + formatNumber(32*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase64gb.innerHTML = "Purchase 64GB Server - $" + formatNumber(64*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase128gb.innerHTML = "Purchase 128GB Server - $" + formatNumber(128*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase256gb.innerHTML = "Purchase 256GB Server - $" + formatNumber(256*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase512gb.innerHTML = "Purchase 512GB Server - $" + formatNumber(512*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase1tb.innerHTML = "Purchase 1TB Server - $" + formatNumber(1024*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase2gb.innerHTML = "Purchase 2GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(2));
purchase4gb.innerHTML = "Purchase 4GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(4));
purchase8gb.innerHTML = "Purchase 8GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(8));
purchase16gb.innerHTML = "Purchase 16GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(16));
purchase32gb.innerHTML = "Purchase 32GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(32));
purchase64gb.innerHTML = "Purchase 64GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(64));
purchase128gb.innerHTML = "Purchase 128GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(128));
purchase256gb.innerHTML = "Purchase 256GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(256));
purchase512gb.innerHTML = "Purchase 512GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(512));
purchase1tb.innerHTML = "Purchase 1TB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(1024));
if (!SpecialServerIps.hasOwnProperty("Darkweb Server")) {
purchaseTor.classList.add("a-link-button");
purchaseTor.classList.remove("a-link-button-bought");
@ -1726,61 +1727,61 @@ function initLocationButtons() {
purchase2gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(2, 2 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(2);
return false;
});
purchase4gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(4, 4 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(4);
return false;
});
purchase8gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(8, 8 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(8);
return false;
});
purchase16gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(16, 16 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(16);
return false;
});
purchase32gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(32, 32 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(32);
return false;
});
purchase64gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(64, 64 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(64);
return false;
});
purchase128gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(128, 128 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(128);
return false;
});
purchase256gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(256, 256 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(256);
return false;
});
purchase512gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(512, 512 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(512);
return false;
});
purchase1tb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(1024, 1024 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(1024);
return false;
});
@ -2264,13 +2265,19 @@ function travelBoxCreate(destCityName, cost) {
yesNoBoxCreate("Would you like to travel to " + destCityName + "? The trip will cost $" + formatNumber(cost, 2) + ".");
}
function purchaseServerBoxCreate(ram, cost) {
function purchaseServerBoxCreate(ram) {
const cost = getPurchaseServerCost(ram);
if (cost === Infinity) {
dialogBoxCreate("Something went wrong when trying to purchase this server. Please contact developer");
return;
}
var yesBtn = yesNoTxtInpBoxGetYesButton();
var noBtn = yesNoTxtInpBoxGetNoButton();
yesBtn.innerHTML = "Purchase Server";
noBtn.innerHTML = "Cancel";
yesBtn.addEventListener("click", function() {
purchaseServer(ram, cost);
purchaseServer(ram);
yesNoTxtInpBoxClose();
});
noBtn.addEventListener("click", function() {

@ -40,6 +40,9 @@ import {Script, findRunningScript, RunningScript,
import {Server, getServer, AddToAllServers,
AllServers, processSingleServerGrowth,
GetServerByHostname, numCycleForGrowth} from "./Server";
import { getPurchaseServerCost,
getPurchaseServerLimit,
getPurchaseServerMaxRam } from "./ServerPurchases";
import {Settings} from "./Settings/Settings";
import {SpecialServerIps} from "./SpecialServerIps";
import {Stock} from "./StockMarket/Stock";
@ -234,24 +237,6 @@ function NetscriptFunctions(workerScript) {
return server.getContract(fn);
}
/**
* @param {number} ram The amount of server RAM to calculate cost of.
* @exception {Error} If the value passed in is not numeric, out of range, or too large of a value.
* @returns {number} The cost of
*/
const getPurchaseServerRamCostGuard = (ram) => {
const guardedRam = Math.round(ram);
if (isNaN(guardedRam) || !isPowerOfTwo(guardedRam)) {
throw Error("failed due to invalid ram argument. Must be numeric and a power of 2");
}
if (guardedRam > CONSTANTS.PurchasedServerMaxRam) {
throw Error("failed because specified RAM was too high. Maximum RAM on a purchased server is " + CONSTANTS.PurchasedServerMaxRam + "GB");
}
return guardedRam * CONSTANTS.BaseCostFor1GBOfRamServer;
};
return {
hacknet : {
numNodes : function() {
@ -1911,7 +1896,7 @@ function NetscriptFunctions(workerScript) {
}
updateDynamicRam("getPurchasedServerLimit", CONSTANTS.ScriptGetPurchasedServerLimit);
return CONSTANTS.PurchasedServerLimit;
return getPurchaseServerLimit();
},
getPurchasedServerMaxRam: function() {
if (workerScript.checkingRam) {
@ -1919,7 +1904,7 @@ function NetscriptFunctions(workerScript) {
}
updateDynamicRam("getPurchasedServerMaxRam", CONSTANTS.ScriptGetPurchasedServerMaxRam);
return CONSTANTS.PurchasedServerMaxRam;
return getPurchaseServerMaxRam();
},
getPurchasedServerCost: function(ram) {
if (workerScript.checkingRam) {
@ -1927,11 +1912,9 @@ function NetscriptFunctions(workerScript) {
}
updateDynamicRam("getPurchasedServerCost", CONSTANTS.ScriptGetPurchaseServerRamCost);
let cost = 0;
try {
cost = getPurchaseServerRamCostGuard(ram);
} catch (e) {
workerScript.scriptRef.log("ERROR: 'getPurchasedServerCost()' " + e.message);
const cost = getPurchaseServerCost(ram);
if (cost === Infinity) {
workerScript.scriptRef.log("ERROR: 'getPurchasedServerCost()' failed due to an invalid 'ram' argument");
return Infinity;
}
@ -1945,26 +1928,23 @@ function NetscriptFunctions(workerScript) {
var hostnameStr = String(hostname);
hostnameStr = hostnameStr.replace(/\s+/g, '');
if (hostnameStr == "") {
workerScript.scriptRef.log("ERROR: Passed empty string for hostname argument of purchaseServer()");
workerScript.log("ERROR: Passed empty string for hostname argument of purchaseServer()");
return "";
}
if (Player.purchasedServers.length >= CONSTANTS.PurchasedServerLimit) {
workerScript.scriptRef.log("ERROR: You have reached the maximum limit of " + CONSTANTS.PurchasedServerLimit +
" servers. You cannot purchase any more.");
if (Player.purchasedServers.length >= getPurchaseServerLimit()) {
workerScript.log(`ERROR: You have reached the maximum limit of ${getPurchaseServerLimit()} servers. You cannot purchase any more.`);
return "";
}
let cost = 0;
try {
cost = getPurchaseServerRamCostGuard(ram);
} catch (e) {
workerScript.scriptRef.log("ERROR: 'purchaseServer()' " + e.message);
return "";
const cost = getPurchaseServerCost(ram);
if (cost === Infinity) {
workerScript.log("ERROR: 'purchaseServer()' failed due to an invalid 'ram' argument");
return Infinity;
}
if (Player.money.lt(cost)) {
workerScript.scriptRef.log("ERROR: Not enough money to purchase server. Need $" + formatNumber(cost, 2));
workerScript.log("ERROR: Not enough money to purchase server. Need $" + formatNumber(cost, 2));
return "";
}
var newServ = new Server({

@ -16,6 +16,7 @@ import { Person,
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Crime } from "../../Crime/Crime";
import { Crimes } from "../../Crime/Crimes";
import { Cities } from "../../Locations/Cities";
@ -42,6 +43,12 @@ export class Sleeve extends Person {
return Generic_fromJSON(Sleeve, value.data);
}
/**
* Stores the type of crime the sleeve is currently attempting
* Must match the name of a Crime object
*/
crimeType: string = "";
/**
* Enum value for current task
*/
@ -136,8 +143,10 @@ export class Sleeve extends Person {
/**
* Commit crimes
*/
commitCrime(p: IPlayer, crime: Crime): boolean {
commitCrime(p: IPlayer, crimeKey: string): boolean {
const crime: Crime | null = Crimes[crimeKey];
if (!(crime instanceof Crime)) { return false; }
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
@ -154,19 +163,7 @@ export class Sleeve extends Person {
this.currentTaskLocation = String(this.gainRatesForTask.money);
// We'll determine success now and adjust the earnings accordingly
if (Math.random() < crime.successRate(p)) {
this.gainRatesForTask.hack *= 2;
this.gainRatesForTask.str *= 2;
this.gainRatesForTask.def *= 2;
this.gainRatesForTask.dex *= 2;
this.gainRatesForTask.agi *= 2;
this.gainRatesForTask.cha *= 2;
} else {
this.gainRatesForTask.money = 0;
}
this.crimeType = crimeKey;
this.currentTaskMaxTime = crime.time;
this.currentTask = SleeveTaskType.Crime;
return true;
@ -181,8 +178,26 @@ export class Sleeve extends Person {
if (this.currentTask === SleeveTaskType.Crime) {
// For crimes, all experience and money is gained at the end
if (this.currentTaskTime >= this.currentTaskMaxTime) {
retValue = this.gainExperience(p, this.gainRatesForTask);
this.gainMoney(p, this.gainRatesForTask);
const crime: Crime | null = Crimes[this.crimeType];
if (!(crime instanceof Crime)) {
console.error(`Invalid data stored in sleeve.crimeType: ${this.crimeType}`);
this.resetTaskStatus();
return retValue;
}
if (Math.random() < crime.successRate(p)) {
// Success
const successGainRates: ITaskTracker = createTaskTracker();
const keysForIteration: (keyof ITaskTracker)[] = (<(keyof ITaskTracker)[]>Object.keys(successGainRates));
for (let i = 0; i < keysForIteration.length; ++i) {
const key = keysForIteration[i];
successGainRates[key] = this.gainRatesForTask[key] * 2;
}
retValue = this.gainExperience(p, successGainRates);
this.gainMoney(p, this.gainRatesForTask);
} else {
retValue = this.gainExperience(p, this.gainRatesForTask);
}
// Do not reset task to IDLE
this.currentTaskTime = 0;
@ -261,7 +276,7 @@ export class Sleeve extends Person {
this.defense_exp += pDefExp;
p.gainDefenseExp(pDefExp);
this.earningsForPlayer.def += pDefExp;
this.earningsForTask.dex += pDefExp;
this.earningsForTask.def += pDefExp;
}
if (pDexExp > 0) {
@ -464,6 +479,9 @@ export class Sleeve extends Person {
this.currentTaskTime = 0;
this.currentTaskMaxTime = 0;
this.factionWorkType = FactionWorkType.None;
this.crimeType = "";
this.currentTaskLocation = "";
this.gymStatType = "";
}
/**
@ -630,12 +648,16 @@ export class Sleeve extends Person {
this.resetTaskStatus();
}
const factionInfo = Factions[factionName].getInfo();
// Set type of work (hacking/field/security), and the experience gains
const sanitizedWorkType: string = workType.toLowerCase();
if (sanitizedWorkType.includes("hack")) {
if (!factionInfo.offerHackingWork) { return false; }
this.factionWorkType = FactionWorkType.Hacking;
this.gainRatesForTask.hack = .15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
} else if (sanitizedWorkType.includes("field")) {
if (!factionInfo.offerFieldWork) { return false; }
this.factionWorkType = FactionWorkType.Field;
this.gainRatesForTask.hack = .1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.str = .1 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
@ -644,6 +666,7 @@ export class Sleeve extends Person {
this.gainRatesForTask.agi = .1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.cha = .1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
} else if (sanitizedWorkType.includes("security")) {
if (!factionInfo.offerSecurityWork) { return false; }
this.factionWorkType = FactionWorkType.Security;
this.gainRatesForTask.hack = .1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.str = .15 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain;

@ -10,8 +10,12 @@ import { IPlayer } from "../IPlayer";
import { CONSTANTS } from "../../Constants";
import { Locations } from "../../Locations";
import { Faction } from "../../Faction/Faction";
import { Factions } from "../../Faction/Factions";
import { FactionWorkType } from "../../Faction/FactionWorkTypeEnum";
import { Cities } from "../../Locations/Cities";
import { Crime } from "../../Crime/Crime";
import { Crimes } from "../../Crime/Crimes";
import { numeralWrapper } from "../../ui/numeralFormat";
@ -23,6 +27,7 @@ import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import { exceptionAlert } from "../../../utils/helpers/exceptionAlert";
import { clearEventListeners } from "../../../utils/uiHelpers/clearEventListeners";
import { createElement } from "../../../utils/uiHelpers/createElement";
import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
import { createPopup } from "../../../utils/uiHelpers/createPopup";
@ -229,7 +234,7 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
popupArguments.push(createElement("p", {
innerText: "Have this sleeve travel to a different city. This affects " +
"the gyms and universities at which this sleeve can study. " +
`Traveling to a different city costs ${numeralWrapper.formatMoney(CONSTANTS.TravelCost)}.` +
`Traveling to a different city costs ${numeralWrapper.formatMoney(CONSTANTS.TravelCost)}. ` +
"It will also CANCEL the sleeve's current task (setting it to idle)",
}));
for (const label in Cities) {
@ -250,6 +255,7 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
sleeve.resetTaskStatus();
removeElementById(popupId);
updateSleeveUi(sleeve, elems);
updateSleeveTaskSelector(sleeve, elems, allSleeves);
return false;
}
}));
@ -401,12 +407,6 @@ function updateSleeveUi(sleeve: Sleeve, elems: ISleeveUIElems) {
}
}
const factionWorkTypeSelectorOptions: string[] = [
"Hacking Contracts",
"Security Work",
"Field Work"
];
const universitySelectorOptions: string[] = [
"Study Computer Science",
"Data Structures",
@ -450,6 +450,7 @@ function updateSleeveTaskSelector(sleeve: Sleeve, elems: ISleeveUIElems, allSlee
// Reset Selectors
removeChildrenFromElement(elems.taskDetailsSelector);
removeChildrenFromElement(elems.taskDetailsSelector2);
elems.taskDetailsSelector2 = clearEventListeners(elems.taskDetailsSelector2!) as HTMLSelectElement;
const value: string = getSelectValue(elems.taskSelector);
switch(value) {
@ -486,29 +487,57 @@ function updateSleeveTaskSelector(sleeve: Sleeve, elems: ISleeveUIElems, allSlee
++factionCount;
}
}
for (let i = 0; i < factionWorkTypeSelectorOptions.length; ++i) {
elems.taskDetailsSelector2!.add(createOptionElement(factionWorkTypeSelectorOptions[i]));
}
// Set initial value for faction work type
switch (sleeve.factionWorkType) {
case FactionWorkType.Hacking:
elems.taskDetailsSelector2!.selectedIndex = 0;
break;
case FactionWorkType.Security:
elems.taskDetailsSelector2!.selectedIndex = 0;
break;
case FactionWorkType.Field:
elems.taskDetailsSelector2!.selectedIndex = 0;
break;
default:
break;
}
// The available faction work types depends on the faction
elems.taskDetailsSelector!.addEventListener("change", () => {
const facName = getSelectValue(elems.taskDetailsSelector!);
const faction: Faction | null = Factions[facName];
if (faction == null) {
console.warn(`Invalid faction name when trying to update Sleeve Task Selector: ${facName}`);
return;
}
const facInfo = faction.getInfo();
removeChildrenFromElement(elems.taskDetailsSelector2!);
let numOptionsAdded = 0;
if (facInfo.offerHackingWork) {
elems.taskDetailsSelector2!.add(createOptionElement("Hacking Contracts"));
if (sleeve.factionWorkType === FactionWorkType.Hacking) {
elems.taskDetailsSelector2!.selectedIndex = numOptionsAdded;
}
++numOptionsAdded;
}
if (facInfo.offerFieldWork) {
elems.taskDetailsSelector2!.add(createOptionElement("Field Work"));
if (sleeve.factionWorkType === FactionWorkType.Field) {
elems.taskDetailsSelector2!.selectedIndex = numOptionsAdded;
}
++numOptionsAdded;
}
if (facInfo.offerSecurityWork) {
elems.taskDetailsSelector2!.add(createOptionElement("Security Work"));
if (sleeve.factionWorkType === FactionWorkType.Security) {
elems.taskDetailsSelector2!.selectedIndex = numOptionsAdded;
}
++numOptionsAdded;
}
});
elems.taskDetailsSelector!.dispatchEvent(new Event("change"));
break;
case "Commit Crime":
let i = 0;
for (const crimeLabel in Crimes) {
const name: string = Crimes[crimeLabel].name;
elems.taskDetailsSelector!.add(createOptionElement(name, crimeLabel));
// Set initial value for crime type
if (sleeve.crimeType === "") { continue; }
const crime: Crime | null = Crimes[sleeve.crimeType];
if (crime == null) { continue; }
if (name === crime!.name) {
elems.taskDetailsSelector!.selectedIndex = i;
}
++i;
}
elems.taskDetailsSelector2!.add(createOptionElement("------"));
@ -614,7 +643,7 @@ function setSleeveTask(sleeve: Sleeve, elems: ISleeveUIElems): boolean {
res = sleeve.workForFaction(playerRef!, detailValue, detailValue2);
break;
case "Commit Crime":
res = sleeve.commitCrime(playerRef!, Crimes[detailValue]);
res = sleeve.commitCrime(playerRef!, detailValue);
break;
case "Take University Course":
res = sleeve.takeUniversityCourse(playerRef!, detailValue2, detailValue);
@ -637,7 +666,15 @@ function setSleeveTask(sleeve: Sleeve, elems: ISleeveUIElems): boolean {
if (res) {
updateSleeveTaskDescription(sleeve, elems);
} else {
elems.taskDescription!.innerText = "Failed to assign sleeve to task. Invalid choice(s).";
switch (taskValue) {
case "Work for Faction":
elems.taskDescription!.innerText = "Failed to assign sleeve to task. This is most likely because the selected faction does not offer the selected work type.";
break;
default:
elems.taskDescription!.innerText = "Failed to assign sleeve to task. Invalid choice(s).";
break;
}
}
if (routing.isOn(Page.Sleeves)) {
@ -672,16 +709,13 @@ function updateSleeveTaskDescription(sleeve: Sleeve, elems: ISleeveUIElems): voi
elems.taskDescription!.innerText = "This sleeve is currently idle";
break;
case "Work for Company":
elems.taskDescription!.innerText = `This sleeve is currently working your ` +
`job at ${sleeve.currentTaskLocation}.`;
elems.taskDescription!.innerText = `This sleeve is currently working your job at ${sleeve.currentTaskLocation}.`;
break;
case "Work for Faction":
elems.taskDescription!.innerText = `This sleeve is currently doing ${detailValue2} for ` +
`${sleeve.currentTaskLocation}.`;
elems.taskDescription!.innerText = `This sleeve is currently doing ${detailValue2} for ${sleeve.currentTaskLocation}.`;
break;
case "Commit Crime":
elems.taskDescription!.innerText = `This sleeve is currently attempting to ` +
`${Crimes[detailValue].type}.`;
elems.taskDescription!.innerText = `This sleeve is currently attempting to ${Crimes[detailValue].type} (Success Rate: ${numeralWrapper.formatPercentage(Crimes[detailValue].successRate(playerRef))}).`;
break;
case "Take University Course":
elems.taskDescription!.innerText = `This sleeve is currently studying/taking a course at ${sleeve.currentTaskLocation}.`;

@ -201,6 +201,7 @@ function PlayerObject() {
// Sleeves & Re-sleeving
this.sleeves = [];
this.resleeves = [];
this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenant
//bitnode
this.bitNodeN = 1;
@ -369,9 +370,7 @@ PlayerObject.prototype.prestigeSourceFile = function() {
this.resleeves = [];
// Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists)
if (this.sleeves.length < SourceFileFlags[10]) {
this.sleeves.length = SourceFileFlags[10];
}
this.sleeves.length = SourceFileFlags[10] + this.sleevesFromCovenant;
for (let i = 0; i < this.sleeves.length; ++i) {
this.sleeves[i] = new Sleeve();
}

@ -41,8 +41,6 @@ function scriptEditorInit() {
return false;
}
// Beautify button
const beautifyButton = createElement("button", {
class: "std-button",
@ -210,8 +208,9 @@ $(document).keydown(function(e) {
function saveAndCloseScriptEditor() {
var filename = document.getElementById("script-editor-filename").value;
let code;
try {
let code = getCurrentEditor().getCode();
code = getCurrentEditor().getCode();
} catch(e) {
dialogBoxCreate("Something went wrong when trying to save (getCurrentEditor().getCode()). Please report to game developer with details");
return;

@ -18,6 +18,7 @@ require("brace/ext/language_tools");
import { NetscriptFunctions } from "../NetscriptFunctions";
import { Settings } from "../Settings/Settings";
import { AceKeybindingSetting } from "../Settings/SettingEnums";
import { clearEventListeners } from "../../utils/uiHelpers/clearEventListeners";
import { createElement } from "../../utils/uiHelpers/createElement";
@ -43,6 +44,7 @@ function validateInitializationParamters(params) {
class AceEditorWrapper extends ScriptEditor {
constructor() {
super();
this.vimCommandDisplayWrapper = null;
}
init(params) {
@ -73,7 +75,7 @@ class AceEditorWrapper extends ScriptEditor {
editorElement.style.fontSize = '16px';
this.editor.setOption("showPrintMargin", false);
//Configure some of the VIM keybindings
// Configure some of the VIM keybindings
ace.config.loadModule('ace/keyboard/vim', function(module) {
var VimApi = module.CodeMirror.Vim;
VimApi.defineEx('write', 'w', function(cm, input) {
@ -90,6 +92,13 @@ class AceEditorWrapper extends ScriptEditor {
});
});
// Store a reference to the VIM command display
this.vimCommandDisplayWrapper = document.getElementById("codemirror-vim-command-display-wrapper");
if (this.vimCommandDisplayWrapper == null) {
console.error(`Could not get Vim Command Display element (id=codemirror-vim-command-display-wrapper)`);
return false;
}
//Function autocompleter
this.editor.setOption("enableBasicAutocompletion", true);
var autocompleter = {
@ -160,6 +169,11 @@ class AceEditorWrapper extends ScriptEditor {
elem.style.display = "block";
}
// Make sure the Vim command display from CodeMirror is invisible
if (this.vimCommandDisplayWrapper instanceof HTMLElement) {
this.vimCommandDisplayWrapper.style.display = "none";
}
// Theme
const themeDropdown = safeClearEventListeners("script-editor-option-theme", "Theme Selector");
removeChildrenFromElement(themeDropdown);
@ -195,10 +209,14 @@ class AceEditorWrapper extends ScriptEditor {
// Keybinding
const keybindingDropdown = safeClearEventListeners("script-editor-option-keybinding", "Keybinding Selector");
removeChildrenFromElement(keybindingDropdown);
keybindingDropdown.add(createOptionElement("Ace", "ace"));
keybindingDropdown.add(createOptionElement("Vim", "vim"));
keybindingDropdown.add(createOptionElement("Emacs", "emacs"));
keybindingDropdown.add(createOptionElement("Ace", AceKeybindingSetting.Ace));
keybindingDropdown.add(createOptionElement("Vim", AceKeybindingSetting.Vim));
keybindingDropdown.add(createOptionElement("Emacs", AceKeybindingSetting.Emacs));
if (Settings.EditorKeybinding) {
// Sanitize the Keybinding setting
if (!(Object.values(AceKeybindingSetting).includes(Settings.EditorKeybinding))) {
Settings.EditorKeybinding = AceKeybindingSetting.Ace;
}
var initialIndex = 0;
for (var i = 0; i < keybindingDropdown.options.length; ++i) {
if (keybindingDropdown.options[i].value === Settings.EditorKeybinding) {

@ -71,6 +71,8 @@ import 'codemirror/keymap/vim.js';
import 'codemirror/keymap/emacs.js';
import 'codemirror/addon/comment/continuecomment.js';
import 'codemirror/addon/dialog/dialog.css';
import 'codemirror/addon/dialog/dialog.js';
import 'codemirror/addon/edit/closebrackets.js';
import 'codemirror/addon/edit/matchbrackets.js';
import 'codemirror/addon/fold/foldcode.js';
@ -79,6 +81,7 @@ import 'codemirror/addon/fold/foldgutter.css';
import 'codemirror/addon/fold/brace-fold.js';
import 'codemirror/addon/fold/indent-fold.js';
import 'codemirror/addon/fold/comment-fold.js';
import 'codemirror/addon/hint/javascript-hint.js';
import 'codemirror/addon/hint/show-hint.js';
import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/addon/lint/lint.js';
@ -87,10 +90,11 @@ import 'codemirror/addon/search/match-highlighter.js';
import 'codemirror/addon/selection/active-line.js';
window.JSHINT = require('jshint').JSHINT;
import './CodeMirrorNetscriptHint.js';
import './CodeMirrorNetscriptLint.js';
import { NetscriptFunctions } from "../NetscriptFunctions";
import { CodeMirrorThemeSetting } from "../Settings/SettingEnums";
import { CodeMirrorKeybindingSetting,
CodeMirrorThemeSetting } from "../Settings/SettingEnums";
import { Settings } from "../Settings/Settings";
import { clearEventListeners } from "../../utils/uiHelpers/clearEventListeners";
@ -114,6 +118,8 @@ function validateInitializationParamters(params) {
class CodeMirrorEditorWrapper extends ScriptEditor {
constructor() {
super();
this.vimCommandDisplay = null;
this.vimCommandDisplayWrapper = null;
this.tabsStyleElement = null;
}
@ -160,6 +166,10 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
this.tabsStyleElement = document.createElement('style');
document.head.appendChild(this.tabsStyleElement);
// Store a reference to the VIM command display
this.vimCommandDisplay = document.getElementById("codemirror-vim-command-display");
this.vimCommandDisplayWrapper = document.getElementById("codemirror-vim-command-display-wrapper");
// Define a "Save" command for CodeMirror so shortcuts like Ctrl + s
// will save in-game
CodeMirror.commands.save = function() { params.saveAndCloseFn(); }
@ -206,6 +216,21 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
return result;
};
// Configure VIM keybindings
var VimApi = CodeMirror.Vim;
VimApi.defineEx('write', 'w', function(cm, input) {
params.saveAndCloseFn();
});
VimApi.defineEx('quit', 'q', function(cm, input) {
params.quitFn();
});
VimApi.defineEx('xwritequit', 'x', function(cm, input) {
params.saveAndCloseFn();
});
VimApi.defineEx('wqwritequit', 'wq', function(cm, input) {
params.saveAndCloseFn();
});
}
initialized() {
@ -237,6 +262,11 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
return;
}
// Get and sanitize the keybinding (keymap) setting
if (!(Object.values(CodeMirrorKeybindingSetting).includes(Settings.EditorKeybinding))) {
Settings.EditorKeybinding = CodeMirrorKeybindingSetting.Default;
}
// Initialize CodeMirror Editor
const textAreaElement = safeGetElementById("codemirror-editor", "CodeMirror Textarea");
const formElement = safeGetElementById("codemirror-form-wrapper", "CodeMirror Form Wrapper");
@ -294,10 +324,10 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
return false;
}
removeChildrenFromElement(keybindingDropdown);
keybindingDropdown.add(createOptionElement("Default", "default"));
keybindingDropdown.add(createOptionElement("Sublime", "sublime"));
keybindingDropdown.add(createOptionElement("Vim", "vim"));
keybindingDropdown.add(createOptionElement("Emacs", "emacs"));
keybindingDropdown.add(createOptionElement("Default", CodeMirrorKeybindingSetting.Default));
keybindingDropdown.add(createOptionElement("Sublime", CodeMirrorKeybindingSetting.Sublime));
keybindingDropdown.add(createOptionElement("Vim", CodeMirrorKeybindingSetting.Vim));
keybindingDropdown.add(createOptionElement("Emacs", CodeMirrorKeybindingSetting.Emacs));
if (Settings.EditorKeybinding) {
var initialIndex = 0;
for (var i = 0; i < keybindingDropdown.options.length; ++i) {
@ -311,14 +341,39 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
keybindingDropdown.selectedIndex = 0;
}
keybindingDropdown.onchange = () => {
// Set Vim command display to be invisible initially
this.vimCommandDisplayWrapper.style.display = "none";
const val = keybindingDropdown.value;
Settings.EditorKeybinding = val;
this.editor.removeKeyMap("sublime");
this.editor.removeKeyMap("emacs");
this.editor.removeKeyMap("vim");
this.editor.removeKeyMap(CodeMirror.keyMap.default);
this.editor.removeKeyMap(CodeMirror.keyMap.sublime);
this.editor.removeKeyMap(CodeMirror.keyMap.emacs);
this.editor.removeKeyMap(CodeMirror.keyMap.vim);
// Setup the VIM command display
let keys = '';
const handleVimKeyPress = (key) => {
keys = keys + key;
this.vimCommandDisplay.innerHTML = keys;
}
const handleVimCommandDone = (e) => {
keys = '';
this.vimCommandDisplay.innerHTML = keys;
}
if (val === CodeMirrorKeybindingSetting.Vim) {
this.vimCommandDisplayWrapper.style.display = "block";
this.editor.on('vim-keypress', handleVimKeyPress);
this.editor.on('vim-command-done', handleVimCommandDone);
} else {
this.vimCommandDisplayWrapper.style.display = "none";
this.editor.off('vim-keypress', handleVimKeyPress);
this.editor.off('vim-command-done', handleVimCommandDone);
}
this.editor.addKeyMap(val);
this.editor.setOption("keyMap", val);
console.log(`Set keymap to ${val} for CodeMirror`);
};
keybindingDropdown.onchange();
@ -489,9 +544,8 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
removeFlexibleOption("script-editor-option-flex4-fieldset");
this.editor.refresh();
console.log(this.editor.options);
} catch(e) {
console.error(`Exception caught: ${e}`);
console.error(`Exception caught: ${e}. ${e.stack}`);
return false;
}
}

@ -33,8 +33,12 @@
}
const sanitizedText = splitText.join("\n");
// Configure JSHINT options
if (!options.indent) // JSHint error.character actually is a column index, this fixes underlining on lines using tabs for indentation
options.indent = 1; // JSHint default value is 4
options.esversion = 6;
JSHINT(sanitizedText, options, options.globals);
var errors = JSHINT.data().errors, result = [];
if (errors) parseErrors(errors, result);

@ -1,16 +1,46 @@
import {CONSTANTS} from "./Constants";
import {Player} from "./Player";
import {Server, AllServers, AddToAllServers} from "./Server";
import {dialogBoxCreate} from "../utils/DialogBox";
import {createRandomIp} from "../utils/IPAddress";
import {yesNoTxtInpBoxGetInput} from "../utils/YesNoBox";
/* Functions to handle any server-related purchasing:
* Purchasing new servers
* Purchasing more RAM for home computer
/**
* Implements functions for purchasing servers or purchasing more RAM for
* the home computer
*/
function purchaseServer(ram, cost) {
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CONSTANTS } from "./Constants";
import { Player } from "./Player";
import { Server,
AllServers,
AddToAllServers} from "./Server";
import { dialogBoxCreate } from "../utils/DialogBox";
import { createRandomIp } from "../utils/IPAddress";
import { yesNoTxtInpBoxGetInput } from "../utils/YesNoBox";
import { isPowerOfTwo } from "../utils/helpers/isPowerOfTwo";
// Returns the cost of purchasing a server with the given RAM
// Returns Infinity for invalid 'ram' arguments
export function getPurchaseServerCost(ram) {
const sanitizedRam = Math.round(ram);
if (isNaN(sanitizedRam) || !isPowerOfTwo(sanitizedRam)) {
return Infinity;
}
if (sanitizedRam > getPurchaseServerMaxRam()) {
return Infinity;
}
return sanitizedRam * CONSTANTS.BaseCostFor1GBOfRamServer * BitNodeMultipliers.PurchasedServerCost;
}
export function getPurchaseServerLimit() {
return Math.round(CONSTANTS.PurchasedServerLimit * BitNodeMultipliers.PurchasedServerLimit);
}
export function getPurchaseServerMaxRam() {
// TODO ensure this is a power of 2?
return Math.round(CONSTANTS.PurchasedServerMaxRam * BitNodeMultipliers.PurchasedServerMaxRam);
}
// Manually purchase a server (NOT through Netscript)
export function purchaseServer(ram) {
const cost = getPurchaseServerCost(ram);
//Check if player has enough money
if (Player.money.lt(cost)) {
dialogBoxCreate("You don't have enough money to purchase this server!");
@ -18,8 +48,8 @@ function purchaseServer(ram, cost) {
}
//Maximum server limit
if (Player.purchasedServers.length >= CONSTANTS.PurchasedServerLimit) {
dialogBoxCreate("You have reached the maximum limit of " + CONSTANTS.PurchasedServerLimit + " servers. " +
if (Player.purchasedServers.length >= getPurchaseServerLimit()) {
dialogBoxCreate("You have reached the maximum limit of " + getPurchaseServerLimit() + " servers. " +
"You cannot purchase any more. You can " +
"delete some of your purchased servers using the deleteServer() Netscript function in a script");
return;
@ -51,8 +81,8 @@ function purchaseServer(ram, cost) {
dialogBoxCreate("Server successfully purchased with hostname " + hostname);
}
function purchaseRamForHomeComputer(cost) {
// Manually upgrade RAM on home computer (NOT through Netscript)
export function purchaseRamForHomeComputer(cost) {
if (Player.money.lt(cost)) {
dialogBoxCreate("You do not have enough money to purchase additional RAM for your home computer");
return;
@ -70,5 +100,3 @@ function purchaseRamForHomeComputer(cost) {
dialogBoxCreate("Purchased additional RAM for home computer! It now has " + homeComputer.maxRam + "GB of RAM.");
}
export {purchaseServer, purchaseRamForHomeComputer};

@ -1,5 +1,27 @@
// Enums that defined allowed values for setting configuration
/**
* Allowed values for 'Keybinding/Keymap' setting in Ace editor
*/
export enum AceKeybindingSetting {
Ace = "ace",
Emacs = "emacs",
Vim = "vim",
}
/**
* Allowed values for 'Keybinding/Keymap' setting in Code Mirror editor
*/
export enum CodeMirrorKeybindingSetting {
Default = "default",
Emacs = "emacs",
Sublime = "sublime",
Vim = "vim",
}
/**
* Allowed values for 'Theme' setting in Code Mirror editor
*/
export enum CodeMirrorThemeSetting {
Monokai = "monokai",
Day_3024 = "3024-day",
@ -57,6 +79,7 @@ export enum CodeMirrorThemeSetting {
Yeti = "yeti",
Zenburn = "zenburn",
}
/**
* Allowed values for the "Editor" setting
*/

@ -1,5 +1,7 @@
import { ISelfInitializer, ISelfLoading } from "../types";
import { CodeMirrorThemeSetting,
import { AceKeybindingSetting,
CodeMirrorKeybindingSetting,
CodeMirrorThemeSetting,
EditorSetting,
OwnedAugmentationsOrderSetting,
PurchaseAugmentationsOrderSetting } from "./SettingEnums";
@ -77,7 +79,7 @@ interface ISettings extends IDefaultSettings {
* The keybinding to use in the script editor.
* TODO: This should really be an enum of allowed values.
*/
EditorKeybinding: string;
EditorKeybinding: AceKeybindingSetting | CodeMirrorKeybindingSetting;
/**
* The theme used in the script editor.
@ -119,7 +121,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
CodeInstructionRunTime: 25,
DisableHotkeys: defaultSettings.DisableHotkeys,
Editor: EditorSetting.Ace,
EditorKeybinding: "ace",
EditorKeybinding: AceKeybindingSetting.Ace,
EditorTheme: "Monokai",
Locale: "en",
MaxLogCapacity: defaultSettings.MaxLogCapacity,

@ -564,6 +564,7 @@ const Engine = {
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");
@ -1580,6 +1581,7 @@ const Engine = {
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;
}
},
@ -1596,6 +1598,7 @@ const Engine = {
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;
}
@ -1641,6 +1644,7 @@ const Engine = {
MainMenuLinks.Sleeves.addEventListener("click", function() {
Engine.loadSleevesContent();
MainMenuLinks.Sleeves.classList.add("active");
return false;
});

@ -122,6 +122,9 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<div id="ace-editor"></div>
<form id="codemirror-form-wrapper"><textarea id="codemirror-editor"></textarea></form>
<div id="codemirror-vim-command-display-wrapper">
Key Buffer: <span id="codemirror-vim-command-display"></span>
</div>
<div id="script-editor-buttons-wrapper"></div> <!-- Buttons below script editor -->
</div> <!-- End wrapper -->

@ -65,7 +65,7 @@ export function initializeMainMenuLinks(): boolean {
MainMenuLinks.Factions = safeGetLink("factions-menu-link");
MainMenuLinks.Augmentations = safeGetLink("augmentations-menu-link");
MainMenuLinks.HacknetNodes = safeGetLink("hacknet-nodes-menu-link");
MainMenuLinks.Sleeves = safeGetLink("sleeves-menu-link");
MainMenuLinks.Sleeves = safeGetLink("sleeves-menu-link");
MainMenuLinks.City = safeGetLink("city-menu-link");
MainMenuLinks.Travel = safeGetLink("travel-menu-link");
MainMenuLinks.Job = safeGetLink("job-menu-link");

@ -5,9 +5,15 @@ import { getElementById } from "./getElementById";
* replacing. Then returns the new cloned element.
* @param elemId The HTML ID to retrieve the element by.
*/
export function clearEventListeners(elemId: string): HTMLElement | null {
export function clearEventListeners(elemId: string | HTMLElement): HTMLElement | null {
try {
const elem: HTMLElement = getElementById(elemId);
let elem: HTMLElement;
if (typeof elemId === "string") {
elem = getElementById(elemId);
} else {
elem = elemId;
}
const newElem: HTMLElement = elem.cloneNode(true) as HTMLElement;
if (elem.parentNode !== null) {
elem.parentNode.replaceChild(newElem, elem);