Added hacknet node api functions for spending hashes. Fixed several bugs with v0.46.0. Rebalanced hash upgrades. continued working on terminal directory implementation

This commit is contained in:
danielyxie 2019-04-05 02:08:41 -07:00
parent fb857642e8
commit 3241945452
30 changed files with 576 additions and 314 deletions

@ -1,5 +1,5 @@
getScriptRam() Netscript Function
===========================
=================================
.. js:function:: getScriptRam(scriptname[, hostname/ip])

@ -0,0 +1,23 @@
hashCost() Netscript Function
=============================
.. warning:: This page contains spoilers for the game
.. js:function:: hashCost(upgName, upgTarget)
:param string upgName: Name of upgrade to get the cost of. Must be an exact match
.. note:: This function is only applicable for Hacknet Servers (the upgraded version
of a Hacknet Node).
Returns the number of hashes required for the specified upgrade. The name of the
upgrade must be an exact match.
Example:
.. code:: javascript
var upgradeName = "Sell for Corporation Funds";
if (hacknet.numHashes() > hacknet.hashCost(upgradeName)) {
hacknet.spendHashes(upgName);
}

@ -0,0 +1,11 @@
numHashes() Netscript Function
==============================
.. warning:: This page contains spoilers for the game
.. js:function:: numHashes()
.. note:: This function is only applicable for Hacknet Servers (the upgraded version
of a Hacknet Node).
Returns the number of hashes you have

@ -0,0 +1,26 @@
spendHashes() Netscript Function
================================
.. warning:: This page contains spoilers for the game
.. js:function:: spendHashes(upgName, upgTarget)
:param string upgName: Name of upgrade to spend hashes on. Must be an exact match
:param string upgTarget: Object to which upgrade applies. Required for certain upgrades
.. note:: This function is only applicable for Hacknet Servers (the upgraded version
of a Hacknet Node).
Spend the hashes generated by your Hacknet Servers on an upgrade. Returns a boolean value -
true if the upgrade is successfully purchased, and false otherwise.
The name of the upgrade must be an exact match. The :code:`upgTarget` argument is used
for upgrades such as :code:`Reduce Minimum Security`, which applies to a specific server.
In this case, the :code:`upgTarget` argument must be the hostname of the server.
Example:
.. code:: javascript
hacknet.spendHashes("Sell for Corporation Funds");
hacknet.spendHashes("Increase Maximum Money", "foodnstuff");

@ -3,6 +3,10 @@
Netscript Hacknet Node API
==========================
.. warning:: Not all functions in the Hacknet Node API are immediately available.
For this reason, the documentation for this API may contains spoilers
for the game.
Netscript provides the following API for accessing and upgrading your Hacknet Nodes
through scripts.
@ -36,6 +40,9 @@ In :ref:`netscriptjs`::
getRamUpgradeCost() <hacknetnodeapi/getRamUpgradeCost>
getCoreUpgradeCost() <hacknetnodeapi/getCoreUpgradeCost>
getCacheUpgradeCost() <hacknetnodeapi/getCacheUpgradeCost>
numHashes() <hacknetnodeapi/numHashes>
hashCost() <hacknetnodeapi/hashCost>
spendHashes() <hacknetnodeapi/spendHashes>
.. _netscript_hacknetnodeapi_referencingahacknetnode:

@ -41,6 +41,9 @@ In :ref:`netscriptjs`::
setToUniversityCourse() <sleeveapi/setToUniversityCourse>
setToGymWorkout() <sleeveapi/setToGymWorkout>
travel() <sleeveapi/travel>
getSleeveAugmentations() <sleeveapi/getSleeveAugmentations>
getSleevePurchasableAugs() <sleeveapi/getSleevePurchasableAugs>
purchaseSleeveAug() <sleeveapi/purchaseSleeveAug>
.. _netscript_sleeveapi_referencingaduplicatesleeve:

@ -1,5 +1,5 @@
getSleevePurchasableAugs() Netscript Function
=======================================
=============================================
.. js:function:: getSleevePurchasableAugs(sleeveNumber)

@ -1,9 +1,10 @@
import {post} from "./ui/postToTerminal";
import { IMap } from "./types";
import { post } from "./ui/postToTerminal";
let Aliases = {};
let GlobalAliases = {};
export let Aliases: IMap<string> = {};
export let GlobalAliases: IMap<string> = {};
function loadAliases(saveString) {
export function loadAliases(saveString: string): void {
if (saveString === "") {
Aliases = {};
} else {
@ -11,7 +12,7 @@ function loadAliases(saveString) {
}
}
function loadGlobalAliases(saveString) {
export function loadGlobalAliases(saveString: string): void {
if (saveString === "") {
GlobalAliases = {};
} else {
@ -20,7 +21,7 @@ function loadGlobalAliases(saveString) {
}
//Print all aliases to terminal
function printAliases() {
export function printAliases(): void {
for (var name in Aliases) {
if (Aliases.hasOwnProperty(name)) {
post("alias " + name + "=" + Aliases[name]);
@ -34,7 +35,7 @@ function printAliases() {
}
//True if successful, false otherwise
function parseAliasDeclaration(dec,global=false) {
export function parseAliasDeclaration(dec: string, global: boolean=false) {
var re = /^([_|\w|!|%|,|@]+)="(.+)"$/;
var matches = dec.match(re);
if (matches == null || matches.length != 3) {return false;}
@ -46,50 +47,53 @@ function parseAliasDeclaration(dec,global=false) {
return true;
}
function addAlias(name, value) {
if (name in GlobalAliases){
function addAlias(name: string, value: string): void {
if (name in GlobalAliases) {
delete GlobalAliases[name];
}
Aliases[name] = value;
}
function addGlobalAlias(name, value) {
function addGlobalAlias(name: string, value: string): void {
if (name in Aliases){
delete Aliases[name];
}
GlobalAliases[name] = value;
}
function getAlias(name) {
function getAlias(name: string): string | null {
if (Aliases.hasOwnProperty(name)) {
return Aliases[name];
}
return null;
}
function getGlobalAlias(name) {
function getGlobalAlias(name: string): string | null {
if (GlobalAliases.hasOwnProperty(name)) {
return GlobalAliases[name];
}
return null;
}
function removeAlias(name) {
export function removeAlias(name: string): boolean {
if (Aliases.hasOwnProperty(name)) {
delete Aliases[name];
return true;
}
if (GlobalAliases.hasOwnProperty(name)) {
delete GlobalAliases[name];
return true;
}
return false;
}
//Returns the original string with any aliases substituted in
//Aliases only applied to "whole words", one level deep
function substituteAliases(origCommand) {
var commandArray = origCommand.split(" ");
export function substituteAliases(origCommand: string): string {
const commandArray = origCommand.split(" ");
if (commandArray.length > 0){
// For the unalias command, dont substite
if (commandArray[0] === "unalias") { return commandArray.join(" "); }
@ -112,6 +116,3 @@ function substituteAliases(origCommand) {
}
return commandArray.join(" ");
}
export {Aliases, GlobalAliases, printAliases, parseAliasDeclaration,
removeAlias, substituteAliases, loadAliases, loadGlobalAliases};

@ -273,21 +273,13 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate:
`
v0.46.0
* Added BitNode-9: Hacktocracy
* Changed BitNode-11's multipliers to make it slightly harder overall
* Source-File 11 is now slightly stronger
* Added several functions to Netscript Sleeve API for buying Sleeve augmentations (by hydroflame)
* Added a new stat for Duplicate Sleeves: Memory
* Increase baseline experience earned from Infiltration, but it now gives diminishing returns (on exp) as you get to higher difficulties/levels
* In Bladeburner, stamina gained from Hyperbolic Regeneration Chamber is now a percentage of your max stamina
* Corporation Changes:
** 'Demand' value of products decreases more slowly
** Bug Fix: Fixed a Corporation issue that broke the Market-TA2 Research
** Bug Fix: Issuing New Shares now works properly
* Bug Fix: Money Statistics tracker was incorrectly recording profits when selling stocks manually
* Bug Fix: Fixed an issue with the job requirement tooltip for security jobs
v0.46.1
* Added numHashes(), hashCost(), and spendHashes() functions to the Netscript Hacknet Node API
* 'Generate Coding Contract' hash upgrade is now more expensive
* 'Generate Coding Contract' hash upgrade now generates the contract randomly on the server, rather than on home computer
* The cost of selling hashes for money no longer increases each time
* Selling hashes for money now costs 4 hashes (in exchange for $1m)
* Bug Fix: Hacknet Node earnings should work properly when game is inactive/offline
* Bug Fix: Duplicate Sleeve augmentations are now properly reset when switching to a new BitNode
`
}

@ -25,7 +25,7 @@ export class MainPanel extends BaseReactComponent {
// We can pass this setter to child components
changeCityState(newCity) {
if (Object.values(Cities).includes(newCity)) {
if (Object.values(CityName).includes(newCity)) {
this.state.city = newCity;
} else {
console.error(`Tried to change MainPanel's city state to an invalid city: ${newCity}`);
@ -45,7 +45,7 @@ export class MainPanel extends BaseReactComponent {
const currentDivision = this.routing().current();
if (currentDivision !== this.state.division) {
this.state.division = currentDivision;
this.state.city = Cities.Sector12;
this.state.city = CityName.Sector12;
}
return this.renderDivisionPage();

@ -54,7 +54,7 @@ export class Overview extends BaseReactComponent {
`Dividends per share: ${numeralWrapper.format(dividendsPerShare, "$0.000a")} / s<br>` +
`Your earnings as a shareholder (Pre-Tax): ${numeralWrapper.format(playerEarnings, "$0.000a")} / s<br>` +
`Dividend Tax Rate: ${this.corp().dividendTaxPercentage}%<br>` +
`Your earnings as a shareholder (Post-Tax): ${numeralWrapper.format(playerEarnings * (1 - (this.corp().dividendTaxPercentage / 100)), "$0.000a")} / s<br>`;
`Your earnings as a shareholder (Post-Tax): ${numeralWrapper.format(playerEarnings * (1 - (this.corp().dividendTaxPercentage / 100)), "$0.000a")} / s<br><br>`;
}
let txt = "Total Funds: " + numeralWrapper.format(this.corp().funds.toNumber(), '$0.000a') + "<br>" +

@ -15,7 +15,7 @@ import { HacknetServer,
import { HashManager } from "./HashManager";
import { HashUpgrades } from "./HashUpgrades";
import { generateRandomContractOnHome } from "../CodingContractGenerator";
import { generateRandomContract } from "../CodingContractGenerator";
import { iTutorialSteps, iTutorialNextStep,
ITutorial} from "../InteractiveTutorial";
import { Player } from "../Player";
@ -45,25 +45,6 @@ export function hasHacknetServers() {
return (Player.bitNodeN === 9 || SourceFileFlags[9] > 0);
}
export function createHacknetServer() {
const numOwned = Player.hacknetNodes.length;
const name = `hacknet-node-${numOwned}`;
const server = new HacknetServer({
adminRights: true,
hostname: name,
player: Player,
});
Player.hacknetNodes.push(server.ip);
// Configure the HacknetServer to actually act as a Server
AddToAllServers(server);
const homeComputer = Player.getHomeComputer();
homeComputer.serversOnNetwork.push(server.ip);
server.serversOnNetwork.push(homeComputer.ip);
return server;
}
export function purchaseHacknet() {
/* INTERACTIVE TUTORIAL */
if (ITutorial.isRunning) {
@ -84,7 +65,7 @@ export function purchaseHacknet() {
if (!Player.canAfford(cost)) { return -1; }
Player.loseMoney(cost);
const server = createHacknetServer();
const server = Player.createHacknetServer();
Player.hashManager.updateCapacity(Player);
return numOwned;
@ -281,9 +262,9 @@ export function processHacknetEarnings(numCycles) {
// call the appropriate function
if (Player.hacknetNodes.length === 0) { return 0; }
if (hasHacknetServers()) {
return processAllHacknetServerEarnings();
return processAllHacknetServerEarnings(numCycles);
} else if (Player.hacknetNodes[0] instanceof HacknetNode) {
return processAllHacknetNodeEarnings();
return processAllHacknetNodeEarnings(numCycles);
} else {
return 0;
}
@ -421,7 +402,7 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
break;
}
case "Generate Coding Contract": {
generateRandomContractOnHome();
generateRandomContract();
break;
}
default:

@ -3,13 +3,13 @@
*/
import { CONSTANTS } from "../Constants";
import { IHacknetNode } from "./IHacknetNode";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { IHacknetNode } from "../Hacknet/IHacknetNode";
import { IPlayer } from "../PersonObjects/IPlayer";
import { BaseServer } from "../Server/BaseServer";
import { RunningScript } from "../Script/RunningScript";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { createRandomIp } from "../../utils/IPAddress";
import { Generic_fromJSON,

@ -151,13 +151,12 @@ export class HashManager {
*/
upgrade(upgName: string): boolean {
const upg = HashUpgrades[upgName];
const currLevel = this.upgrades[upgName];
if (upg == null || currLevel == null) {
if (upg == null) {
console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`);
return false;
}
const cost = upg.getCost(currLevel);
const cost = this.getUpgradeCost(upgName);
if (this.hashes < cost) {
return false;

@ -2,6 +2,7 @@
* Object representing an upgrade that can be purchased with hashes
*/
export interface IConstructorParams {
cost?: number;
costPerLevel: number;
desc: string;
hasTargetServer?: boolean;
@ -10,6 +11,14 @@ export interface IConstructorParams {
}
export class HashUpgrade {
/**
* If the upgrade has a flat cost (never increases), it goes here
* Otherwise, this property should be undefined
*
* This property overrides the 'costPerLevel' property
*/
cost?: number;
/**
* Base cost for this upgrade. Every time the upgrade is purchased,
* its cost increases by this same amount (so its 1x, 2x, 3x, 4x, etc.)
@ -35,6 +44,8 @@ export class HashUpgrade {
value: number = 0;
constructor(p: IConstructorParams) {
if (p.cost != null) { this.cost = p.cost; }
this.costPerLevel = p.costPerLevel;
this.desc = p.desc;
this.hasTargetServer = p.hasTargetServer ? p.hasTargetServer : false;
@ -43,6 +54,8 @@ export class HashUpgrade {
}
getCost(levels: number): number {
if (typeof this.cost === "number") { return this.cost; }
return Math.round((levels + 1) * this.costPerLevel);
}
}

@ -3,7 +3,8 @@ import { IConstructorParams } from "../HashUpgrade";
export const HashUpgradesMetadata: IConstructorParams[] = [
{
costPerLevel: 1,
cost: 4,
costPerLevel: 4,
desc: "Sell hashes for $1m",
name: "Sell for Money",
value: 1e6,
@ -62,8 +63,8 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
value: 10,
},
{
costPerLevel: 150,
desc: "Generate a random Coding Contract on your home computer",
costPerLevel: 200,
desc: "Generate a random Coding Contract somewhere on the network",
name: "Generate Coding Contract",
value: 1,
},

@ -78,7 +78,6 @@ export class CompanyLocation extends React.Component<IProps, IState> {
this.applyForSoftwareConsultantJob = this.applyForSoftwareConsultantJob.bind(this);
this.applyForSoftwareJob = this.applyForSoftwareJob.bind(this);
this.applyForWaiterJob = this.applyForWaiterJob.bind(this);
this.checkIfEmployedHere = this.checkIfEmployedHere.bind(this);
this.startInfiltration = this.startInfiltration.bind(this);
this.work = this.work.bind(this);
@ -102,70 +101,70 @@ export class CompanyLocation extends React.Component<IProps, IState> {
applyForAgentJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForAgentJob();
this.checkIfEmployedHere();
this.checkIfEmployedHere(true);
}
applyForBusinessConsultantJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForBusinessConsultantJob();
this.checkIfEmployedHere();
this.checkIfEmployedHere(true);
}
applyForBusinessJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForBusinessJob();
this.checkIfEmployedHere();
this.checkIfEmployedHere(true);
}
applyForEmployeeJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForEmployeeJob();
this.checkIfEmployedHere();
this.checkIfEmployedHere(true);
}
applyForItJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForItJob();
this.checkIfEmployedHere();
this.checkIfEmployedHere(true);
}
applyForPartTimeEmployeeJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForPartTimeEmployeeJob();
this.checkIfEmployedHere();
this.checkIfEmployedHere(true);
}
applyForPartTimeWaiterJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForPartTimeWaiterJob();
this.checkIfEmployedHere();
this.checkIfEmployedHere(true);
}
applyForSecurityJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForSecurityJob();
this.checkIfEmployedHere();
this.checkIfEmployedHere(true);
}
applyForSoftwareConsultantJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForSoftwareConsultantJob();
this.checkIfEmployedHere();
this.checkIfEmployedHere(true);
}
applyForSoftwareJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForSoftwareJob();
this.checkIfEmployedHere();
this.checkIfEmployedHere(true);
}
applyForWaiterJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForWaiterJob();
this.checkIfEmployedHere();
this.checkIfEmployedHere(true);
}
checkIfEmployedHere(updateState=true) {
checkIfEmployedHere(updateState=false) {
this.jobTitle = this.props.p.jobs[this.props.locName];
if (this.jobTitle != null) {
this.companyPosition = CompanyPositions[this.jobTitle];

@ -36,12 +36,12 @@ import { netscriptCanGrow,
import { getCostOfNextHacknetNode,
getCostOfNextHacknetServer,
purchaseHacknet,
hasHacknetServers } from "./Hacknet/HacknetHelpers";
hasHacknetServers,
purchaseHashUpgrade } from "./Hacknet/HacknetHelpers";
import { CityName } from "./Locations/data/CityNames";
import { LocationName } from "./Locations/data/LocationNames";
import { HacknetServer } from "./Hacknet/HacknetServer";
import {Locations} from "./Locations";
import { Message } from "./Message/Message";
import { Messages } from "./Message/MessageHelpers";
import {inMission} from "./Missions";
@ -363,6 +363,19 @@ function NetscriptFunctions(workerScript) {
if (!hasHacknetServers()) { return Infinity; }
const node = getHacknetNode(i);
return node.calculateCacheUpgradeCost(n);
},
numHashes : function() {
if (!hasHacknetServers()) { return 0; }
return Player.hashManager.hashes;
},
hashCost : function(upgName) {
if (!hasHacknetServers()) { return Infinity; }
return Player.hashManager.getUpgradeCost(upgName);
},
spendHashes : function(upgName, upgTarget) {
if (!hasHacknetServers()) { return false; }
return purchaseHashUpgrade(upgName, upgTarget);
}
},
sprintf : sprintf,
@ -2852,10 +2865,6 @@ function NetscriptFunctions(workerScript) {
AddToAllServers(darkweb);
SpecialServerIps.addIp("Darkweb Server", darkweb.ip);
const purchaseTor = document.getElementById("location-purchase-tor");
purchaseTor.setAttribute("class", "a-link-button-bought");
purchaseTor.innerHTML = "TOR Router - Purchased";
Player.getHomeComputer().serversOnNetwork.push(darkweb.ip);
darkweb.serversOnNetwork.push(Player.getHomeComputer().ip);
Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain);

@ -123,6 +123,7 @@ export interface IPlayer {
gainAgilityExp(exp: number): void;
gainCharismaExp(exp: number): void;
gainMoney(money: number): void;
getCurrentServer(): Server;
getHomeComputer(): Server;
getNextCompanyPosition(company: Company, entryPosType: CompanyPosition): CompanyPosition;
getUpgradeHomeRamCost(): number;

@ -1631,9 +1631,8 @@ export function applyForJob(entryPosType, sing=false) {
document.getElementById("world-menu-header").click();
if (sing) { return true; }
dialogBoxCreate("Congratulations! You were offered a new job at " + this.companyName + " as a " + pos.name + "!");
Engine.loadLocationContent(false);
dialogBoxCreate("Congratulations! You were offered a new job at " + this.companyName + " as a " + pos.name + "!");
}
//Returns your next position at a company given the field (software, business, etc.)
@ -1734,7 +1733,6 @@ export function applyForEmployeeJob(sing=false) {
document.getElementById("world-menu-header").click();
if (sing) {return true;}
dialogBoxCreate("Congratulations, you are now employed at " + this.companyName);
Engine.loadLocationContent(false);
} else {
if (sing) {return false;}
dialogBoxCreate("Unforunately, you do not qualify for this position");
@ -1750,7 +1748,6 @@ export function applyForPartTimeEmployeeJob(sing=false) {
document.getElementById("world-menu-header").click();
if (sing) {return true;}
dialogBoxCreate("Congratulations, you are now employed part-time at " + this.companyName);
Engine.loadLocationContent(false);
} else {
if (sing) {return false;}
dialogBoxCreate("Unforunately, you do not qualify for this position");
@ -1766,7 +1763,6 @@ export function applyForWaiterJob(sing=false) {
document.getElementById("world-menu-header").click();
if (sing) {return true;}
dialogBoxCreate("Congratulations, you are now employed as a waiter at " + this.companyName);
Engine.loadLocationContent(false);
} else {
if (sing) {return false;}
dialogBoxCreate("Unforunately, you do not qualify for this position");
@ -1782,7 +1778,6 @@ export function applyForPartTimeWaiterJob(sing=false) {
document.getElementById("world-menu-header").click();
if (sing) {return true;}
dialogBoxCreate("Congratulations, you are now employed as a part-time waiter at " + this.companyName);
Engine.loadLocationContent(false);
} else {
if (sing) {return false;}
dialogBoxCreate("Unforunately, you do not qualify for this position");

@ -1,8 +1,14 @@
/**
* Server and HacknetServer-related methods for the Player class (PlayerObject)
*/
import { IPlayer } from "../IPlayer";
import { CONSTANTS } from "../../Constants";
import { AllServers } from "../../Server/AllServers";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { HacknetServer } from "../../Hacknet/HacknetServer";
import { AddToAllServers,
AllServers } from "../../Server/AllServers";
import { SpecialServerIps } from "../../Server/SpecialServerIps";
export function hasTorRouter(this: IPlayer) {
@ -28,3 +34,22 @@ export function getUpgradeHomeRamCost(this: IPlayer) {
var cost = currentRam * CONSTANTS.BaseCostFor1GBOfRamHome * mult * BitNodeMultipliers.HomeComputerRamCost;
return cost;
}
export function createHacknetServer(this: IPlayer): HacknetServer {
const numOwned = this.hacknetNodes.length;
const name = `hacknet-node-${numOwned}`;
const server = new HacknetServer({
adminRights: true,
hostname: name,
player: this,
});
this.hacknetNodes.push(server.ip);
// Configure the HacknetServer to actually act as a Server
AddToAllServers(server);
const homeComputer = this.getHomeComputer();
homeComputer.serversOnNetwork.push(server.ip);
server.serversOnNetwork.push(homeComputer.ip);
return server;
}

@ -435,20 +435,30 @@ export class Sleeve extends Person {
* Called on every sleeve for a Source File prestige
*/
prestige(p: IPlayer) {
// Reset exp
this.hacking_exp = 0;
this.strength_exp = 0;
this.defense_exp = 0;
this.dexterity_exp = 0;
this.agility_exp = 0;
this.charisma_exp = 0;
// Reset task-related stuff
this.resetTaskStatus();
this.earningsForSleeves = createTaskTracker();
this.earningsForPlayer = createTaskTracker();
this.logs = [];
this.shockRecovery(p);
// Reset augs and multipliers
this.augmentations = [];
this.resetMultipliers();
// Reset sleeve-related stats
this.shock = 1;
this.storedCycles = 0;
this.sync = Math.max(this.memory, 1);
this.shockRecovery(p);
this.logs = [];
}
/**

@ -14,7 +14,6 @@ import { Faction } from "./Faction/Faction";
import { Factions,
initFactions } from "./Faction/Factions";
import { joinFaction } from "./Faction/FactionHelpers";
import { createHacknetServer } from "./Hacknet/HacknetHelpers";
import {deleteGangDisplayContent} from "./Gang";
import { Message } from "./Message/Message";
import { initMessages,
@ -97,9 +96,6 @@ function prestigeAugmentation() {
//Re-create foreign servers
initForeignServers(Player.getHomeComputer());
//Darkweb is purchase-able
document.getElementById("location-purchase-tor").setAttribute("class", "a-link-button");
//Gain favor for Companies
for (var member in Companies) {
if (Companies.hasOwnProperty(member)) {
@ -340,7 +336,8 @@ function prestigeSourceFile() {
// Source-File 9 (level 3) effect
if (SourceFileFlags[9] >= 3) {
const hserver = createHacknetServer();
const hserver = Player.createHacknetServer();
hserver.level = 100;
hserver.cores = 10;
hserver.cache = 5;

@ -17,6 +17,7 @@ import { AllServers } from "../Server/AllServers";
import { processSingleServerGrowth } from "../Server/ServerHelpers";
import { Settings } from "../Settings/Settings";
import { EditorSetting } from "../Settings/SettingEnums";
import { isValidFilename } from "../Terminal/DirectoryHelpers";
import {TextFile} from "../TextFile";
import {Page, routing} from "../ui/navigationTracking";
@ -247,7 +248,7 @@ function saveAndCloseScriptEditor() {
return;
}
if (checkValidFilename(filename) == false) {
if (filename !== ".fconf" && !isValidFilename(filename)) {
dialogBoxCreate("Script filename can contain only alphanumerics, hyphens, and underscores");
return;
}
@ -292,17 +293,6 @@ function saveAndCloseScriptEditor() {
Engine.loadTerminalContent();
}
//Checks that the string contains only valid characters for a filename, which are alphanumeric,
// underscores, hyphens, and dots
function checkValidFilename(filename) {
var regex = /^[.a-zA-Z0-9\/_-]+$/;
if (filename.match(regex)) {
return true;
}
return false;
}
//Called when the game is loaded. Loads all running scripts (from all servers)
//into worker scripts so that they will start running
export function loadAllRunningScripts() {

@ -16,7 +16,7 @@ import { Reviver } from "../../utils/JSONReviver";
export let AllServers: IMap<Server | HacknetServer> = {};
// Saftely add a Server to the AllServers map
export function AddToAllServers(server: Server): void {
export function AddToAllServers(server: Server | HacknetServer): void {
var serverIp = server.ip;
if (ipExists(serverIp)) {
console.log("IP of server that's being added: " + serverIp);

@ -6,7 +6,7 @@ import { GetServerByHostname } from "./ServerHelpers";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { createRandomString } from "../utils/createRandomString";
import { createRandomString } from "../utils/helpers/createRandomString";
import { createRandomIp } from "../../utils/IPAddress";
import { Generic_fromJSON,
Generic_toJSON,

@ -1,7 +1,16 @@
import {substituteAliases, printAliases,
parseAliasDeclaration,
removeAlias, GlobalAliases,
Aliases} from "./Alias";
import {
evaluateDirectoryPath,
isValidDirectoryPath,
isValidFilename
} from "./Terminal/DirectoryHelpers";
import { determineAllPossibilitiesForTabCompletion } from "./Terminal/determineAllPossibilitiesForTabCompletion";
import { Aliases,
GlobalAliases,
parseAliasDeclaration,
printAliases,
removeAlias,
substituteAliases } from "./Alias";
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import {CodingContract, CodingContractResult,
CodingContractRewardType} from "./CodingContracts";
@ -85,12 +94,13 @@ $(document).keydown(function(event) {
if (event.keyCode === KEY.ENTER) {
event.preventDefault(); //Prevent newline from being entered in Script Editor
var command = terminalInput.value;
const command = terminalInput.value;
const dir = Terminal.currDir + "/";
post(
"<span class='prompt'>[" +
(FconfSettings.ENABLE_TIMESTAMPS ? getTimestamp() + " " : "") +
Player.getCurrentServer().hostname +
" ~]&gt;</span> " + command
` ~${dir}]&gt;</span> ${command}`
);
if (command.length > 0) {
@ -183,7 +193,7 @@ $(document).keydown(function(event) {
var commandArray = input.split(" ");
var index = commandArray.length - 2;
if (index < -1) {index = 0;}
var allPos = determineAllPossibilitiesForTabCompletion(input, index);
var allPos = determineAllPossibilitiesForTabCompletion(Player, input, index);
if (allPos.length == 0) {return;}
var arg = "";
@ -393,178 +403,8 @@ function tabCompletion(command, arg, allPossibilities, index=0) {
}
}
function determineAllPossibilitiesForTabCompletion(input, index=0) {
var allPos = [];
allPos = allPos.concat(Object.keys(GlobalAliases));
var currServ = Player.getCurrentServer();
input = input.toLowerCase();
//If the command starts with './' and the index == -1, then the user
//has input ./partialexecutablename so autocomplete the script or program
//Put './' in front of each script/executable
if (input.startsWith("./") && index == -1) {
//All programs and scripts
for (var i = 0; i < currServ.scripts.length; ++i) {
allPos.push("./" + currServ.scripts[i].filename);
}
//Programs are on home computer
var homeComputer = Player.getHomeComputer();
for(var i = 0; i < homeComputer.programs.length; ++i) {
allPos.push("./" + homeComputer.programs[i]);
}
return allPos;
}
//Autocomplete the command
if (index == -1) {
return ["alias", "analyze", "cat", "check", "clear", "cls", "connect", "download", "expr",
"free", "hack", "help", "home", "hostname", "ifconfig", "kill", "killall",
"ls", "lscpu", "mem", "nano", "ps", "rm", "run", "scan", "scan-analyze",
"scp", "sudov", "tail", "theme", "top"].concat(Object.keys(Aliases)).concat(Object.keys(GlobalAliases));
}
if (input.startsWith ("buy ")) {
let options = [];
for(const i in DarkWebItems) {
const item = DarkWebItems[i]
options.push(item.program);
}
return options.concat(Object.keys(GlobalAliases));
}
if (input.startsWith("scp ") && index == 1) {
for (var iphostname in AllServers) {
if (AllServers.hasOwnProperty(iphostname)) {
allPos.push(AllServers[iphostname].ip);
allPos.push(AllServers[iphostname].hostname);
}
}
}
if (input.startsWith("scp ") && index == 0) {
//All Scripts and lit files
for (var i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
for (var i = 0; i < currServ.messages.length; ++i) {
if (!(currServ.messages[i] instanceof Message)) {
allPos.push(currServ.messages[i]);
}
}
for (var i = 0; i < currServ.textFiles.length; ++i) {
allPos.push(currServ.textFiles[i].fn);
}
}
if (input.startsWith("connect ") || input.startsWith("telnet ")) {
//All network connections
for (var i = 0; i < currServ.serversOnNetwork.length; ++i) {
var serv = AllServers[currServ.serversOnNetwork[i]];
if (serv == null) {continue;}
allPos.push(serv.ip); //IP
allPos.push(serv.hostname); //Hostname
}
return allPos;
}
if (input.startsWith("kill ") || input.startsWith("tail ") ||
input.startsWith("mem ") || input.startsWith("check ")) {
//All Scripts
for (var i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
return allPos;
}
if (input.startsWith("nano ")) {
//Scripts and text files and .fconf
for (var i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
for (var i = 0; i < currServ.textFiles.length; ++i) {
allPos.push(currServ.textFiles[i].fn);
}
allPos.push(".fconf");
return allPos;
}
if (input.startsWith("rm ")) {
for (let i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
for (let i = 0; i < currServ.programs.length; ++i) {
allPos.push(currServ.programs[i]);
}
for (let i = 0; i < currServ.messages.length; ++i) {
if (!(currServ.messages[i] instanceof Message) && isString(currServ.messages[i]) &&
currServ.messages[i].endsWith(".lit")) {
allPos.push(currServ.messages[i]);
}
}
for (let i = 0; i < currServ.textFiles.length; ++i) {
allPos.push(currServ.textFiles[i].fn);
}
for (let i = 0; i < currServ.contracts.length; ++i) {
allPos.push(currServ.contracts[i].fn);
}
return allPos;
}
if (input.startsWith("run ")) {
//All programs, scripts, and contracts
for (let i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
//Programs are on home computer
var homeComputer = Player.getHomeComputer();
for (let i = 0; i < homeComputer.programs.length; ++i) {
allPos.push(homeComputer.programs[i]);
}
for (let i = 0; i < currServ.contracts.length; ++i) {
allPos.push(currServ.contracts[i].fn);
}
return allPos;
}
if (input.startsWith("cat ")) {
for (var i = 0; i < currServ.messages.length; ++i) {
if (currServ.messages[i] instanceof Message) {
allPos.push(currServ.messages[i].filename);
} else {
allPos.push(currServ.messages[i]);
}
}
for (var i = 0; i < currServ.textFiles.length; ++i) {
allPos.push(currServ.textFiles[i].fn);
}
return allPos;
}
if (input.startsWith("download ")) {
for (var i = 0; i < currServ.textFiles.length; ++i) {
allPos.push(currServ.textFiles[i].fn);
}
for (var i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
}
if (input.startsWith("ls ")) {
for (var i = 0; i < currServ.textFiles.length; ++i) {
allPos.push(currServ.textFiles[i].fn);
}
for (var i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
}
return allPos;
}
let Terminal = {
//Flags to determine whether the player is currently running a hack or an analyze
// Flags to determine whether the player is currently running a hack or an analyze
hackFlag: false,
analyzeFlag: false,
actionStarted: false,
@ -573,7 +413,12 @@ let Terminal = {
commandHistory: [],
commandHistoryIndex: 0,
contractOpen: false, //True if a Coding Contract prompt is opened
// True if a Coding Contract prompt is opened
contractOpen: false,
// Full Path of current directory
// Excludes the trailing forward slash
currDir: "",
resetTerminalInput: function() {
if (FconfSettings.WRAP_INPUT) {
@ -1104,7 +949,7 @@ let Terminal = {
postError("You need to be able to connect to the Dark Web to use the buy command. (Maybe there's a TOR router you can buy somewhere)");
}
break;
case "cat":
case "cat": {
if (commandArray.length !== 2) {
postError("Incorrect usage of cat command. Usage: cat [file]");
return;
@ -1131,6 +976,34 @@ let Terminal = {
}
postError(`No such file ${filename}`);
break;
}
case "cd": {
if (commandArray.length !== 2) {
postError("Incorrect number of arguments. Usage: cd [dir]");
} else {
let dir = commandArray[1];
// Ignore trailing slashes
if (dir.endsWith("/")) {
dir = dir.slice(0, -1);
}
// If the path begins with a slash, then its an absolute path. Otherwise its relative
if (dir.startsWith("/")) {
// TODO
} else {
// TODO
}
evaledDir = evaluateDirectoryPath(dir);
if (evaledDir == null) {
postError("Invalid path");
return;
}
}
break;
}
case "check":
if (commandArray.length < 2) {
postError("Incorrect number of arguments. Usage: check [script] [arg1] [arg2]...");
@ -1687,28 +1560,35 @@ let Terminal = {
},
executeListCommand: function(commandArray) {
if (commandArray.length !== 1 && commandArray.length !== 2 && commandArray.length !== 4) {
postError("Incorrect usage of ls command. Usage: ls [| grep pattern]");
return;
const numArgs = commandArray.length;
function incorrectUsage() {
postError("Incorrect usage of ls command. Usage: ls [dir] [| grep pattern]");
}
// grep
let filter = null;
let prefix = null;
if (commandArray.length === 4) {
if (commandArray[1] === "|" && commandArray[2] === "grep") {
if (commandArray[3] !== " ") {
filter = commandArray[3];
}
} else {
postError("Incorrect usage of ls command. Usage: ls [| grep pattern]");
return;
if (numArgs <= 0 || numArgs > 5 || numArgs === 3) {
return incorrectUsage();
}
let filter = null; // Grep
let prefix = null; // Directory path
// If there are 4+ arguments, then the last 3 must be for grep
if (numArgs >= 4) {
if (commandArray[numArgs - 2] !== "grep" || commandArray[numArgs - 3] !== "|") {
return incorrectUsage();
}
} else if (commandArray.length === 2) { // want to ls a folder
filter = commandArray[numArgs - 1];
}
// If the second argument is not a pipe, then it must be for listing a directory
if (numArgs >= 2 && commandArray[1] !== "|") {
prefix = commandArray[1];
if (!prefix.endsWith("/")) {
prefix += "/";
}
if (!isValidDirectoryPath(prefix)) {
return incorrectUsage();
}
}
//Display all programs and scripts
@ -1728,12 +1608,15 @@ let Terminal = {
}
}
// If the fn includes a forward slash, it must be in a subdirectory.
// Therefore, we only list the "first" directory in its path
if (fn.includes("/")) {
fn = fn.split("/")[0]+"/";
fn = fn.split("/")[0] + "/";
if (folders.includes(fn)) {
return;
}
folders.push(fn);
return;
}
allFiles.push(fn);

@ -0,0 +1,121 @@
/**
* Helper functions that implement "directory" functionality in the Terminal.
* These aren't real directories, they're more of a pseudo-directory implementation
*/
/**
* Checks whether a string is a valid filename. Only used for the filename itself,
* not the entire filepath
*/
export function isValidFilename(filename: string): boolean {
// Allows alphanumerics, hyphens, underscores.
// Must have a file exntesion
const regex = /^[.a-zA-Z0-9_-]+[.][.a-zA-Z0-9_-]+$/;
// match() returns null if no match is found
return filename.match(regex) != null;
}
/**
* Checks whether a string is a valid directory name. Only used for the directory itself,
* not an entire path
*/
export function isValidDirectoryName(name: string): boolean {
// Allows alphanumerics, hyphens and underscores.
// Name can begin with a single period, but otherwise cannot have any
const regex = /^.?[a-zA-Z0-9_-]+$/;
// match() returns null if no match is found
return name.match(regex) != null;
}
/**
* Checks whether a string is a valid directory path.
* This only checks if it has the proper formatting. It does NOT check
* if the directories actually exist on Terminal
*/
export function isValidDirectoryPath(path: string): boolean {
let t_path: string = path;
if (t_path.length === 0) { return false; }
if (t_path.length === 1) {
return isValidDirectoryName(t_path);
}
// Leading/Trailing slashes dont matter for this
if (t_path.startsWith("/")) { t_path = t_path.slice(1); }
if (t_path.endsWith("/")) { t_path = t_path.slice(0, -1); }
// Check that every section of the path is a valid directory name
const dirs = t_path.split("/");
for (const dir of dirs) {
// Special case, "." and ".." are valid for path
if (dir === "." || dir === "..") { continue; }
if (!isValidDirectoryName(dir)) {
return false;
}
}
return true;
}
/**
* Checks whether a string is a valid file path. This only checks if it has the
* proper formatting. It dose NOT check if the file actually exists on Terminal
*/
export function isValidFilePath(path: string): boolean {
let t_path = path;
// Impossible for filename to have less than length of 3
if (t_path.length < 3) { return false; }
// Filename can't end with trailing slash. Leading slash can be ignored
if (t_path.endsWith("")) { return false; }
if (t_path.startsWith("/")) { t_path = t_path.slice(1); }
// Everything after the last forward slash is the filename. Everything before
// it is the file path
const fnSeparator = t_path.lastIndexOf("/");
if (fnSeparator === -1) {
return isValidFilename(t_path);
}
const fn = t_path.slice(fnSeparator + 1);
const dirPath = t_path.slice(0, fnSeparator);
return (isValidDirectoryPath(dirPath) && isValidFilename(fn));
}
/**
* Evaluates a directory path, including the processing of linux dots.
* Returns the full, proper path, or null if an invalid path is passed in
*/
export function evaluateDirectoryPath(path: string): string | null {
if (!isValidDirectoryPath(path)) { return null; }
let t_path = path;
// Trim leading/trailing slashes
if (t_path.startsWith("/")) { t_path = t_path.slice(1); }
if (t_path.endsWith("/")) { t_path = t_path.slice(0, -1); }
const dirs = t_path.split("/");
const reconstructedPath: string[] = [];
for (const dir of dirs) {
if (dir === ".") {
// Current directory, do nothing
continue;
} else if (dir === "..") {
// Parent directory
const res = reconstructedPath.pop();
if (res == null) {
return null; // Array was empty, invalid path
}
} else {
reconstructedPath.push(dir);
}
}
return reconstructedPath.join("/");
}

@ -0,0 +1,175 @@
import { Aliases,
GlobalAliases } from "../Alias";
import { DarkWebItems } from "../DarkWeb/DarkWebItems";
import { Message } from "../Message/Message";
import { IPlayer } from "../PersonObjects/IPlayer"
import { AllServers } from "../Server/AllServers";
export function determineAllPossibilitiesForTabCompletion(p: IPlayer, input: string, index: number=0): string[] {
let allPos: string[] = [];
allPos = allPos.concat(Object.keys(GlobalAliases));
const currServ = p.getCurrentServer();
const homeComputer = p.getHomeComputer();
input = input.toLowerCase();
//If the command starts with './' and the index == -1, then the user
//has input ./partialexecutablename so autocomplete the script or program
//Put './' in front of each script/executable
if (input.startsWith("./") && index == -1) {
//All programs and scripts
for (var i = 0; i < currServ.scripts.length; ++i) {
allPos.push("./" + currServ.scripts[i].filename);
}
//Programs are on home computer
for(var i = 0; i < homeComputer.programs.length; ++i) {
allPos.push("./" + homeComputer.programs[i]);
}
return allPos;
}
//Autocomplete the command
if (index == -1) {
return ["alias", "analyze", "cat", "check", "clear", "cls", "connect", "download", "expr",
"free", "hack", "help", "home", "hostname", "ifconfig", "kill", "killall",
"ls", "lscpu", "mem", "nano", "ps", "rm", "run", "scan", "scan-analyze",
"scp", "sudov", "tail", "theme", "top"].concat(Object.keys(Aliases)).concat(Object.keys(GlobalAliases));
}
if (input.startsWith("buy ")) {
let options = [];
for (const i in DarkWebItems) {
const item = DarkWebItems[i]
options.push(item.program);
}
return options.concat(Object.keys(GlobalAliases));
}
if (input.startsWith("scp ") && index == 1) {
for (var iphostname in AllServers) {
if (AllServers.hasOwnProperty(iphostname)) {
allPos.push(AllServers[iphostname].ip);
allPos.push(AllServers[iphostname].hostname);
}
}
}
if (input.startsWith("scp ") && index == 0) {
//All Scripts and lit files
for (var i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
for (var i = 0; i < currServ.messages.length; ++i) {
if (!(currServ.messages[i] instanceof Message)) {
allPos.push(<string>currServ.messages[i]);
}
}
for (var i = 0; i < currServ.textFiles.length; ++i) {
allPos.push(currServ.textFiles[i].fn);
}
}
if (input.startsWith("connect ") || input.startsWith("telnet ")) {
//All network connections
for (var i = 0; i < currServ.serversOnNetwork.length; ++i) {
var serv = AllServers[currServ.serversOnNetwork[i]];
if (serv == null) {continue;}
allPos.push(serv.ip); //IP
allPos.push(serv.hostname); //Hostname
}
return allPos;
}
if (input.startsWith("kill ") || input.startsWith("tail ") ||
input.startsWith("mem ") || input.startsWith("check ")) {
//All Scripts
for (var i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
return allPos;
}
if (input.startsWith("nano ")) {
//Scripts and text files and .fconf
for (var i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
for (var i = 0; i < currServ.textFiles.length; ++i) {
allPos.push(currServ.textFiles[i].fn);
}
allPos.push(".fconf");
return allPos;
}
if (input.startsWith("rm ")) {
for (let i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
for (let i = 0; i < currServ.programs.length; ++i) {
allPos.push(currServ.programs[i]);
}
for (let i = 0; i < currServ.messages.length; ++i) {
if (!(currServ.messages[i] instanceof Message)) {
allPos.push(<string>currServ.messages[i]);
}
}
for (let i = 0; i < currServ.textFiles.length; ++i) {
allPos.push(currServ.textFiles[i].fn);
}
for (let i = 0; i < currServ.contracts.length; ++i) {
allPos.push(currServ.contracts[i].fn);
}
return allPos;
}
if (input.startsWith("run ")) {
//All programs, scripts, and contracts
for (let i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
//Programs are on home computer
for (let i = 0; i < homeComputer.programs.length; ++i) {
allPos.push(homeComputer.programs[i]);
}
for (let i = 0; i < currServ.contracts.length; ++i) {
allPos.push(currServ.contracts[i].fn);
}
return allPos;
}
if (input.startsWith("cat ")) {
for (var i = 0; i < currServ.messages.length; ++i) {
if (currServ.messages[i] instanceof Message) {
const msg: Message = <Message>currServ.messages[i];
allPos.push(msg.filename);
} else {
allPos.push(<string>currServ.messages[i]);
}
}
for (var i = 0; i < currServ.textFiles.length; ++i) {
allPos.push(currServ.textFiles[i].fn);
}
return allPos;
}
if (input.startsWith("download ")) {
for (var i = 0; i < currServ.textFiles.length; ++i) {
allPos.push(currServ.textFiles[i].fn);
}
for (var i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
}
if (input.startsWith("ls ")) {
for (var i = 0; i < currServ.textFiles.length; ++i) {
allPos.push(currServ.textFiles[i].fn);
}
for (var i = 0; i < currServ.scripts.length; ++i) {
allPos.push(currServ.scripts[i].filename);
}
}
return allPos;
}