This commit is contained in:
Olivier Gagnon 2021-10-04 22:31:07 -04:00
commit c5e29dafc4
38 changed files with 1352 additions and 880 deletions

32
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -38,7 +38,13 @@ export function CityTabs(props: IProps): React.ReactElement {
</Tabs> </Tabs>
{city !== "Expand" ? ( {city !== "Expand" ? (
<Industry rerender={props.rerender} city={city} warehouse={division.warehouses[city]} office={office} /> <Industry
key={city}
rerender={props.rerender}
city={city}
warehouse={division.warehouses[city]}
office={office}
/>
) : ( ) : (
<ExpandNewCity cityStateSetter={setCity} /> <ExpandNewCity cityStateSetter={setCity} />
)} )}

@ -122,7 +122,7 @@ function WarehouseRoot(props: IProps): React.ReactElement {
</> </>
); );
} }
console.log(division.products);
for (const prodName in division.products) { for (const prodName in division.products) {
const prod = division.products[prodName]; const prod = division.products[prodName];
if (prod === undefined) continue; if (prod === undefined) continue;

@ -25,7 +25,7 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
if (n === null) return <></>; if (n === null) return <></>;
const r = ResearchMap[n.text]; const r = ResearchMap[n.text];
let disabled = division.sciResearch.qty < r.cost; let disabled = division.sciResearch.qty < r.cost || n.researched;
const parent = n.parent; const parent = n.parent;
if (parent !== null) { if (parent !== null) {
disabled = disabled || !parent.researched; disabled = disabled || !parent.researched;

@ -11,10 +11,11 @@ Source-File minus 1 is extremely weak because it can be fully level up quickly.
*/ */
export enum Exploit { export enum Exploit {
UndocumentedFunctionCall = "UndocumentedFunctionCall",
Unclickable = "Unclickable",
PrototypeTampering = "PrototypeTampering",
Bypass = "Bypass", Bypass = "Bypass",
PrototypeTampering = "PrototypeTampering",
Unclickable = "Unclickable",
UndocumentedFunctionCall = "UndocumentedFunctionCall",
TimeCompression = "TimeCompression",
// To the players reading this. Yes you're supposed to add EditSaveFile by // To the players reading this. Yes you're supposed to add EditSaveFile by
// editing your save file, yes you could add them all, no we don't care // editing your save file, yes you could add them all, no we don't care
// that's not the point. // that's not the point.
@ -24,11 +25,12 @@ export enum Exploit {
const names: { const names: {
[key: string]: string; [key: string]: string;
} = { } = {
UndocumentedFunctionCall: "by looking beyond the documentation.", Bypass: "by circumventing the ram cost of document.",
EditSaveFile: "by editing your save file.", EditSaveFile: "by editing your save file.",
PrototypeTampering: "by tampering with Numbers prototype.", PrototypeTampering: "by tampering with Numbers prototype.",
TimeCompression: "by compressing time",
Unclickable: "by clicking the unclickable.", Unclickable: "by clicking the unclickable.",
Bypass: "by circumventing the ram cost of document.", UndocumentedFunctionCall: "by looking beyond the documentation.",
}; };
export function ExploitName(exploit: string): string { export function ExploitName(exploit: string): string {

35
src/Exploits/loops.ts Normal file

@ -0,0 +1,35 @@
import { Player } from "../Player";
import { Exploit } from "./Exploit";
function tampering(): void {
if (Player.exploits.includes(Exploit.PrototypeTampering)) return;
// Tampering
const a = 55;
setInterval(function () {
if (a.toExponential() !== "5.5e+1") {
Player.giveExploit(Exploit.PrototypeTampering);
}
}, 15 * 60 * 1000); // 15 minutes
}
function timeCompression(): void {
if (Player.exploits.includes(Exploit.TimeCompression)) return;
// Time compression
let last = new Date().getTime();
function minute(): void {
const now = new Date().getTime();
if (now - last < 500) {
// time has been compressed.
Player.giveExploit(Exploit.TimeCompression);
return;
}
last = now;
setTimeout(minute, 1000);
}
setTimeout(minute, 1000);
}
export function startExploits(): void {
tampering();
timeCompression();
}

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

@ -1,12 +1,10 @@
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { isString } from "./utils/helpers/isString"; import { isString } from "./utils/helpers/isString";
import { AllServers } from "./Server/AllServers"; import { AllServers } from "./Server/AllServers";
import { WorkerScript } from "./Netscript/WorkerScript"; import { WorkerScript } from "./Netscript/WorkerScript";
export function netscriptDelay(time: number, workerScript: WorkerScript): Promise<void> { export function netscriptDelay(time: number, workerScript: WorkerScript): Promise<void> {
return new Promise(function (resolve) { return new Promise(function (resolve) {
workerScript.delay = setTimeoutRef(() => { workerScript.delay = window.setTimeout(() => {
workerScript.delay = null; workerScript.delay = null;
resolve(); resolve();
}, time); }, time);

@ -54,13 +54,9 @@ import {
calculateWeakenTime, calculateWeakenTime,
} from "./Hacking"; } from "./Hacking";
import { calculateServerGrowth } from "./Server/formulas/grow"; import { calculateServerGrowth } from "./Server/formulas/grow";
import { Gang } from "./Gang/Gang";
import { AllGangs } from "./Gang/AllGangs"; import { AllGangs } from "./Gang/AllGangs";
import { GangMemberTasks } from "./Gang/GangMemberTasks";
import { GangMemberUpgrades } from "./Gang/GangMemberUpgrades";
import { Factions, factionExists } from "./Faction/Factions"; import { Factions, factionExists } from "./Faction/Factions";
import { joinFaction, purchaseAugmentation } from "./Faction/FactionHelpers"; import { joinFaction, purchaseAugmentation } from "./Faction/FactionHelpers";
import { FactionWorkType } from "./Faction/FactionWorkTypeEnum";
import { netscriptCanGrow, netscriptCanHack, netscriptCanWeaken } from "./Hacking/netscriptCanHack"; import { netscriptCanGrow, netscriptCanHack, netscriptCanWeaken } from "./Hacking/netscriptCanHack";
import { import {
@ -136,13 +132,9 @@ import { workerScripts } from "./Netscript/WorkerScripts";
import { WorkerScript } from "./Netscript/WorkerScript"; import { WorkerScript } from "./Netscript/WorkerScript";
import { makeRuntimeRejectMsg, netscriptDelay, resolveNetscriptRequestedThreads } from "./NetscriptEvaluator"; import { makeRuntimeRejectMsg, netscriptDelay, resolveNetscriptRequestedThreads } from "./NetscriptEvaluator";
import { Interpreter } from "./ThirdParty/JSInterpreter"; import { Interpreter } from "./ThirdParty/JSInterpreter";
import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { findSleevePurchasableAugs } from "./PersonObjects/Sleeve/SleeveHelpers";
import { Exploit } from "./Exploits/Exploit";
import { Router } from "./ui/GameRoot"; import { Router } from "./ui/GameRoot";
import { numeralWrapper } from "./ui/numeralFormat"; import { numeralWrapper } from "./ui/numeralFormat";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { is2DArray } from "./utils/helpers/is2DArray"; import { is2DArray } from "./utils/helpers/is2DArray";
import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions";
@ -162,13 +154,13 @@ import { Augmentation } from "./Augmentation/Augmentation";
import { HacknetNode } from "./Hacknet/HacknetNode"; import { HacknetNode } from "./Hacknet/HacknetNode";
import { CodingContract } from "./CodingContracts"; import { CodingContract } from "./CodingContracts";
import { GangMember } from "./Gang/GangMember";
import { GangMemberTask } from "./Gang/GangMemberTask";
import { Stock } from "./StockMarket/Stock"; import { Stock } from "./StockMarket/Stock";
import { BaseServer } from "./Server/BaseServer"; import { BaseServer } from "./Server/BaseServer";
import { INetscriptGang, NetscriptGang } from "./NetscriptFunctions/Gang";
import { staneksGift } from "./CotMG/Helper"; import { INetscriptSleeve, NetscriptSleeve } from "./NetscriptFunctions/Sleeve";
import { Fragments, FragmentById } from "./CotMG/Fragment"; import { INetscriptExtra, NetscriptExtra } from "./NetscriptFunctions/Extra";
import { INetscriptHacknet, NetscriptHacknet } from "./NetscriptFunctions/Hacknet";
import { INetscriptStanek, NetscriptStanek } from "./NetscriptFunctions/Stanek";
const defaultInterpreter = new Interpreter("", () => undefined); const defaultInterpreter = new Interpreter("", () => undefined);
@ -205,8 +197,12 @@ function toNative(pseudoObj: any): any {
return nativeObj; return nativeObj;
} }
interface NS { interface NS extends INetscriptExtra {
[key: string]: any; [key: string]: any;
hacknet: INetscriptHacknet;
gang: INetscriptGang;
sleeve: INetscriptSleeve;
stanek: INetscriptStanek;
} }
function NetscriptFunctions(workerScript: WorkerScript): NS { function NetscriptFunctions(workerScript: WorkerScript): NS {
@ -373,35 +369,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
} }
}; };
// Utility function to get Hacknet Node object
const getHacknetNode = function (i: any, callingFn = ""): HacknetNode | HacknetServer {
if (isNaN(i)) {
throw makeRuntimeErrorMsg(callingFn, "Invalid index specified for Hacknet Node: " + i);
}
if (i < 0 || i >= Player.hacknetNodes.length) {
throw makeRuntimeErrorMsg(callingFn, "Index specified for Hacknet Node is out-of-bounds: " + i);
}
if (hasHacknetServers(Player)) {
const hi = Player.hacknetNodes[i];
if (typeof hi !== "string") throw new Error("hacknet node was not a string");
const hserver = AllServers[hi];
if (!(hserver instanceof HacknetServer)) throw new Error("hacknet server was not actually hacknet server");
if (hserver == null) {
throw makeRuntimeErrorMsg(
callingFn,
`Could not get Hacknet Server for index ${i}. This is probably a bug, please report to game dev`,
);
}
return hserver;
} else {
const node = Player.hacknetNodes[i];
if (!(node instanceof HacknetNode)) throw new Error("hacknet node was not node.");
return node;
}
};
const makeRuntimeErrorMsg = function (caller: string, msg: string): string { const makeRuntimeErrorMsg = function (caller: string, msg: string): string {
const errstack = new Error().stack; const errstack = new Error().stack;
if (errstack === undefined) throw new Error("how did we not throw an error?"); if (errstack === undefined) throw new Error("how did we not throw an error?");
@ -517,23 +484,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
} }
}; };
const checkSleeveAPIAccess = function (func: any): void {
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeErrorMsg(
`sleeve.${func}`,
"You do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10",
);
}
};
const checkSleeveNumber = function (func: any, sleeveNumber: any): void {
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
const msg = `Invalid sleeve number: ${sleeveNumber}`;
workerScript.log(func, msg);
throw makeRuntimeErrorMsg(`sleeve.${func}`, msg);
}
};
const getCodingContract = function (func: any, ip: any, fn: any): CodingContract { const getCodingContract = function (func: any, ip: any, fn: any): CodingContract {
const server = safeGetServer(ip, func); const server = safeGetServer(ip, func);
const contract = server.getContract(fn); const contract = server.getContract(fn);
@ -553,31 +503,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
} }
}; };
const checkGangApiAccess = function (func: any): void {
const gang = Player.gang;
if (gang === null) throw new Error("Must have joined gang");
const hasAccess = gang instanceof Gang;
if (!hasAccess) {
throw makeRuntimeErrorMsg(`gang.${func}`, `You do not currently have a Gang`);
}
};
const getGangMember = function (func: any, name: any): GangMember {
const gang = Player.gang;
if (gang === null) throw new Error("Must have joined gang");
for (const member of gang.members) if (member.name === name) return member;
throw makeRuntimeErrorMsg(`gang.${func}`, `Invalid gang member: '${name}'`);
};
const getGangTask = function (func: any, name: any): GangMemberTask {
const task = GangMemberTasks[name];
if (!task) {
throw makeRuntimeErrorMsg(`gang.${func}`, `Invalid task: '${name}'`);
}
return task;
};
const getBladeburnerActionObject = function (func: any, type: any, name: any): any { const getBladeburnerActionObject = function (func: any, type: any, name: any): any {
const bladeburner = Player.bladeburner; const bladeburner = Player.bladeburner;
if (bladeburner === null) throw new Error("Must have joined bladeburner"); if (bladeburner === null) throw new Error("Must have joined bladeburner");
@ -796,142 +721,32 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
return out; return out;
}; };
const functions = { const helper = {
hacknet: { updateDynamicRam: updateDynamicRam,
numNodes: function (): any { makeRuntimeErrorMsg: makeRuntimeErrorMsg,
return Player.hacknetNodes.length; string: (funcName: string, argName: string, v: any): string => {
if (typeof v === "string") return v;
if (typeof v === "number") return v + ""; // cast to string;
throw makeRuntimeErrorMsg(funcName, `${argName} should be a string`);
}, },
maxNumNodes: function (): any { number: (funcName: string, argName: string, v: any): number => {
if (hasHacknetServers(Player)) { if (typeof v === "number") return v;
return HacknetServerConstants.MaxServers; if (!isNaN(v) && !isNaN(parseFloat(v))) return parseFloat(v);
} throw makeRuntimeErrorMsg(funcName, `${argName} should be a number`);
return Infinity;
}, },
purchaseNode: function (): any { boolean: (v: any): boolean => {
return purchaseHacknet(Player); return !!v; // Just convert it to boolean.
}, },
getPurchaseNodeCost: function (): any {
if (hasHacknetServers(Player)) {
return getCostOfNextHacknetServer(Player);
} else {
return getCostOfNextHacknetNode(Player);
}
},
getNodeStats: function (i: any): any {
const node = getHacknetNode(i, "getNodeStats");
const hasUpgraded = hasHacknetServers(Player);
const res: any = {
name: node instanceof HacknetServer ? node.hostname : node.name,
level: node.level,
ram: node instanceof HacknetServer ? node.maxRam : node.ram,
cores: node.cores,
production: node instanceof HacknetServer ? node.hashRate : node.moneyGainRatePerSecond,
timeOnline: node.onlineTimeSeconds,
totalProduction: node instanceof HacknetServer ? node.totalHashesGenerated : node.totalMoneyGenerated,
}; };
if (hasUpgraded && node instanceof HacknetServer) { const gang = NetscriptGang(Player, workerScript, helper);
res.cache = node.cache; const sleeve = NetscriptSleeve(Player, workerScript, helper);
res.hashCapacity = node.hashCapacity; const extra = NetscriptExtra(Player, workerScript, helper);
} const hacknet = NetscriptHacknet(Player, workerScript, helper);
const stanek = NetscriptStanek(Player, workerScript, helper);
return res; const functions = {
}, hacknet: hacknet,
upgradeLevel: function (i: any, n: any): any {
const node = getHacknetNode(i, "upgradeLevel");
return purchaseLevelUpgrade(Player, node, n);
},
upgradeRam: function (i: any, n: any): any {
const node = getHacknetNode(i, "upgradeRam");
return purchaseRamUpgrade(Player, node, n);
},
upgradeCore: function (i: any, n: any): any {
const node = getHacknetNode(i, "upgradeCore");
return purchaseCoreUpgrade(Player, node, n);
},
upgradeCache: function (i: any, n: any): any {
if (!hasHacknetServers(Player)) {
return false;
}
const node = getHacknetNode(i, "upgradeCache");
if (!(node instanceof HacknetServer)) {
workerScript.log("upgradeCache", "Can only be called on hacknet servers");
return false;
}
const res = purchaseCacheUpgrade(Player, node, n);
if (res) {
updateHashManagerCapacity(Player);
}
return res;
},
getLevelUpgradeCost: function (i: any, n: any): any {
const node = getHacknetNode(i, "upgradeLevel");
return node.calculateLevelUpgradeCost(n, Player.hacknet_node_level_cost_mult);
},
getRamUpgradeCost: function (i: any, n: any): any {
const node = getHacknetNode(i, "upgradeRam");
return node.calculateRamUpgradeCost(n, Player.hacknet_node_ram_cost_mult);
},
getCoreUpgradeCost: function (i: any, n: any): any {
const node = getHacknetNode(i, "upgradeCore");
return node.calculateCoreUpgradeCost(n, Player.hacknet_node_core_cost_mult);
},
getCacheUpgradeCost: function (i: any, n: any): any {
if (!hasHacknetServers(Player)) {
return Infinity;
}
const node = getHacknetNode(i, "upgradeCache");
if (!(node instanceof HacknetServer)) {
workerScript.log("getCacheUpgradeCost", "Can only be called on hacknet servers");
return -1;
}
return node.calculateCacheUpgradeCost(n);
},
numHashes: function (): any {
if (!hasHacknetServers(Player)) {
return 0;
}
return Player.hashManager.hashes;
},
hashCapacity: function (): any {
if (!hasHacknetServers(Player)) {
return 0;
}
return Player.hashManager.capacity;
},
hashCost: function (upgName: any): any {
if (!hasHacknetServers(Player)) {
return Infinity;
}
return Player.hashManager.getUpgradeCost(upgName);
},
spendHashes: function (upgName: any, upgTarget: any): any {
if (!hasHacknetServers(Player)) {
return false;
}
return purchaseHashUpgrade(Player, upgName, upgTarget);
},
getHashUpgradeLevel: function (upgName: any): any {
const level = Player.hashManager.upgrades[upgName];
if (level === undefined) {
throw makeRuntimeErrorMsg("hacknet.hashUpgradeLevel", `Invalid Hash Upgrade: ${upgName}`);
}
return level;
},
getStudyMult: function (): any {
if (!hasHacknetServers(Player)) {
return false;
}
return Player.hashManager.getStudyMult();
},
getTrainingMult: function (): any {
if (!hasHacknetServers(Player)) {
return false;
}
return Player.hashManager.getTrainingMult();
},
},
sprintf: sprintf, sprintf: sprintf,
vsprintf: vsprintf, vsprintf: vsprintf,
scan: function (ip: any = workerScript.serverIp, hostnames: any = true): any { scan: function (ip: any = workerScript.serverIp, hostnames: any = true): any {
@ -1057,7 +872,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
return Promise.reject(workerScript); return Promise.reject(workerScript);
} }
const moneyBefore = server.moneyAvailable <= 0 ? 1 : server.moneyAvailable; const moneyBefore = server.moneyAvailable <= 0 ? 1 : server.moneyAvailable;
server.moneyAvailable += 1 * threads; // It can be grown even if it has no money
processSingleServerGrowth(server, threads, Player, host.cpuCores); processSingleServerGrowth(server, threads, Player, host.cpuCores);
const moneyAfter = server.moneyAvailable; const moneyAfter = server.moneyAvailable;
workerScript.scriptRef.recordGrow(server.ip, threads); workerScript.scriptRef.recordGrow(server.ip, threads);
@ -1401,7 +1215,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
} }
const spawnDelay = 10; const spawnDelay = 10;
setTimeoutRef(() => { setTimeout(() => {
if (isNaN(threads) || threads <= 0) { if (isNaN(threads) || threads <= 0) {
throw makeRuntimeErrorMsg("spawn", `Invalid thread count. Must be numeric and > 0, is ${threads}`); throw makeRuntimeErrorMsg("spawn", `Invalid thread count. Must be numeric and > 0, is ${threads}`);
} }
@ -4084,7 +3898,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
checkSingularityAccess("softReset", 3); checkSingularityAccess("softReset", 3);
workerScript.log("softReset", "Soft resetting. This will cause this script to be killed"); workerScript.log("softReset", "Soft resetting. This will cause this script to be killed");
setTimeoutRef(() => { setTimeout(() => {
prestigeAugmentation(); prestigeAugmentation();
runAfterReset(cbScript); runAfterReset(cbScript);
}, 0); }, 0);
@ -4103,7 +3917,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
} }
Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain);
workerScript.log("installAugmentations", "Installing Augmentations. This will cause this script to be killed"); workerScript.log("installAugmentations", "Installing Augmentations. This will cause this script to be killed");
setTimeoutRef(() => { setTimeout(() => {
installAugmentations(); installAugmentations();
runAfterReset(cbScript); runAfterReset(cbScript);
}, 0); }, 0);
@ -4113,259 +3927,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
}, },
// Gang API // Gang API
gang: { gang: gang,
createGang: function (faction: any): any {
updateDynamicRam("createGang", getRamCost("gang", "createGang"));
// this list is copied from Faction/ui/Root.tsx
const GangNames = [
"Slum Snakes",
"Tetrads",
"The Syndicate",
"The Dark Army",
"Speakers for the Dead",
"NiteSec",
"The Black Hand",
];
if (!Player.canAccessGang() || !GangNames.includes(faction)) return false;
if (Player.inGang()) return false;
if (!Player.factions.includes(faction)) return false;
const isHacking = faction === "NiteSec" || faction === "The Black Hand";
Player.startGang(faction, isHacking);
return true;
},
inGang: function (): any {
updateDynamicRam("inGang", getRamCost("gang", "inGang"));
return Player.inGang();
},
getMemberNames: function (): any {
updateDynamicRam("getMemberNames", getRamCost("gang", "getMemberNames"));
checkGangApiAccess("getMemberNames");
const gang = Player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return gang.members.map((member) => member.name);
},
getGangInformation: function (): any {
updateDynamicRam("getGangInformation", getRamCost("gang", "getGangInformation"));
checkGangApiAccess("getGangInformation");
const gang = Player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return {
faction: gang.facName,
isHacking: gang.isHackingGang,
moneyGainRate: gang.moneyGainRate,
power: gang.getPower(),
respect: gang.respect,
respectGainRate: gang.respectGainRate,
territory: gang.getTerritory(),
territoryClashChance: gang.territoryClashChance,
territoryWarfareEngaged: gang.territoryWarfareEngaged,
wantedLevel: gang.wanted,
wantedLevelGainRate: gang.wantedGainRate,
};
},
getOtherGangInformation: function (): any {
updateDynamicRam("getOtherGangInformation", getRamCost("gang", "getOtherGangInformation"));
checkGangApiAccess("getOtherGangInformation");
const cpy: any = {};
for (const gang in AllGangs) {
cpy[gang] = Object.assign({}, AllGangs[gang]);
}
return cpy;
},
getMemberInformation: function (name: any): any {
updateDynamicRam("getMemberInformation", getRamCost("gang", "getMemberInformation"));
checkGangApiAccess("getMemberInformation");
const member = getGangMember("getMemberInformation", name);
return {
name: member.name,
task: member.task,
earnedRespect: member.earnedRespect,
hack: member.hack,
str: member.str,
def: member.def,
dex: member.dex,
agi: member.agi,
cha: member.cha,
hack_exp: member.hack_exp,
str_exp: member.str_exp,
def_exp: member.def_exp,
dex_exp: member.dex_exp,
agi_exp: member.agi_exp,
cha_exp: member.cha_exp,
hack_mult: member.hack_mult,
str_mult: member.str_mult,
def_mult: member.def_mult,
dex_mult: member.dex_mult,
agi_mult: member.agi_mult,
cha_mult: member.cha_mult,
hack_asc_mult: member.calculateAscensionMult(member.hack_asc_points),
str_asc_mult: member.calculateAscensionMult(member.str_asc_points),
def_asc_mult: member.calculateAscensionMult(member.def_asc_points),
dex_asc_mult: member.calculateAscensionMult(member.dex_asc_points),
agi_asc_mult: member.calculateAscensionMult(member.agi_asc_points),
cha_asc_mult: member.calculateAscensionMult(member.cha_asc_points),
hack_asc_points: member.hack_asc_points,
str_asc_points: member.str_asc_points,
def_asc_points: member.def_asc_points,
dex_asc_points: member.dex_asc_points,
agi_asc_points: member.agi_asc_points,
cha_asc_points: member.cha_asc_points,
upgrades: member.upgrades.slice(),
augmentations: member.augmentations.slice(),
};
},
canRecruitMember: function (): any {
updateDynamicRam("canRecruitMember", getRamCost("gang", "canRecruitMember"));
checkGangApiAccess("canRecruitMember");
const gang = Player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return gang.canRecruitMember();
},
recruitMember: function (name: any): any {
updateDynamicRam("recruitMember", getRamCost("gang", "recruitMember"));
checkGangApiAccess("recruitMember");
const gang = Player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const recruited = gang.recruitMember(name);
if (recruited) {
workerScript.log("recruitMember", `Successfully recruited Gang Member '${name}'`);
} else {
workerScript.log("recruitMember", `Failed to recruit Gang Member '${name}'`);
}
return recruited;
},
getTaskNames: function (): any {
updateDynamicRam("getTaskNames", getRamCost("gang", "getTaskNames"));
checkGangApiAccess("getTaskNames");
const gang = Player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const tasks = gang.getAllTaskNames();
tasks.unshift("Unassigned");
return tasks;
},
setMemberTask: function (memberName: any, taskName: any): any {
updateDynamicRam("setMemberTask", getRamCost("gang", "setMemberTask"));
checkGangApiAccess("setMemberTask");
const member = getGangMember("setMemberTask", memberName);
const success = member.assignToTask(taskName);
if (success) {
workerScript.log("setMemberTask", `Successfully assigned Gang Member '${memberName}' to '${taskName}' task`);
} else {
workerScript.log(
"setMemberTask",
`Failed to assign Gang Member '${memberName}' to '${taskName}' task. '${memberName}' is now Unassigned`,
);
}
return success;
},
getTaskStats: function (taskName: any): any {
updateDynamicRam("getTaskStats", getRamCost("gang", "getTaskStats"));
checkGangApiAccess("getTaskStats");
const task = getGangTask("getTaskStats", taskName);
const copy = Object.assign({}, task);
copy.territory = Object.assign({}, task.territory);
return copy;
},
getEquipmentNames: function (): any {
updateDynamicRam("getEquipmentNames", getRamCost("gang", "getEquipmentNames"));
checkGangApiAccess("getEquipmentNames");
return Object.keys(GangMemberUpgrades);
},
getEquipmentCost: function (equipName: any): any {
updateDynamicRam("getEquipmentCost", getRamCost("gang", "getEquipmentCost"));
checkGangApiAccess("getEquipmentCost");
const gang = Player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const upg = GangMemberUpgrades[equipName];
if (upg === null) return Infinity;
return gang.getUpgradeCost(upg);
},
getEquipmentType: function (equipName: any): any {
updateDynamicRam("getEquipmentType", getRamCost("gang", "getEquipmentType"));
checkGangApiAccess("getEquipmentType");
const upg = GangMemberUpgrades[equipName];
if (upg == null) return "";
return upg.getType();
},
getEquipmentStats: function (equipName: any): any {
updateDynamicRam("getEquipmentStats", getRamCost("gang", "getEquipmentStats"));
checkGangApiAccess("getEquipmentStats");
const equipment = GangMemberUpgrades[equipName];
if (!equipment) {
throw makeRuntimeErrorMsg("getEquipmentStats", `Invalid equipment: ${equipName}`);
}
return Object.assign({}, equipment.mults);
},
purchaseEquipment: function (memberName: any, equipName: any): any {
updateDynamicRam("purchaseEquipment", getRamCost("gang", "purchaseEquipment"));
checkGangApiAccess("purchaseEquipment");
const gang = Player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("purchaseEquipment", memberName);
const equipment = GangMemberUpgrades[equipName];
if (!equipment) return false;
const res = member.buyUpgrade(equipment, Player, gang);
if (res) {
workerScript.log("purchaseEquipment", `Purchased '${equipName}' for Gang member '${memberName}'`);
} else {
workerScript.log("purchaseEquipment", `Failed to purchase '${equipName}' for Gang member '${memberName}'`);
}
return res;
},
ascendMember: function (name: any): any {
updateDynamicRam("ascendMember", getRamCost("gang", "ascendMember"));
checkGangApiAccess("ascendMember");
const gang = Player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("ascendMember", name);
if (!member.canAscend()) return;
return gang.ascendMember(member, workerScript);
},
setTerritoryWarfare: function (engage: any): any {
updateDynamicRam("setTerritoryWarfare", getRamCost("gang", "setTerritoryWarfare"));
checkGangApiAccess("setTerritoryWarfare");
const gang = Player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (engage) {
gang.territoryWarfareEngaged = true;
workerScript.log("setTerritoryWarfare", "Engaging in Gang Territory Warfare");
} else {
gang.territoryWarfareEngaged = false;
workerScript.log("setTerritoryWarfare", "Disengaging in Gang Territory Warfare");
}
},
getChanceToWinClash: function (otherGang: any): any {
updateDynamicRam("getChanceToWinClash", getRamCost("gang", "getChanceToWinClash"));
checkGangApiAccess("getChanceToWinClash");
const gang = Player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (AllGangs[otherGang] == null) {
throw makeRuntimeErrorMsg(`gang.getChanceToWinClash`, `Invalid gang: ${otherGang}`);
}
const playerPower = AllGangs[gang.facName].power;
const otherPower = AllGangs[otherGang].power;
return playerPower / (otherPower + playerPower);
},
getBonusTime: function (): any {
updateDynamicRam("getBonusTime", getRamCost("gang", "getBonusTime"));
checkGangApiAccess("getBonusTime");
const gang = Player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return Math.round(gang.storedCycles / 5);
},
}, // end gang namespace
// Bladeburner API // Bladeburner API
bladeburner: { bladeburner: {
@ -4941,286 +4503,10 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
}, // End coding contracts }, // End coding contracts
// Duplicate Sleeve API // Duplicate Sleeve API
sleeve: { sleeve: sleeve,
getNumSleeves: function (): any {
updateDynamicRam("getNumSleeves", getRamCost("sleeve", "getNumSleeves"));
checkSleeveAPIAccess("getNumSleeves");
return Player.sleeves.length;
},
setToShockRecovery: function (sleeveNumber: any = 0): any {
updateDynamicRam("setToShockRecovery", getRamCost("sleeve", "setToShockRecovery"));
checkSleeveAPIAccess("setToShockRecovery");
checkSleeveNumber("setToShockRecovery", sleeveNumber);
return Player.sleeves[sleeveNumber].shockRecovery(Player);
},
setToSynchronize: function (sleeveNumber: any = 0): any {
updateDynamicRam("setToSynchronize", getRamCost("sleeve", "setToSynchronize"));
checkSleeveAPIAccess("setToSynchronize");
checkSleeveNumber("setToSynchronize", sleeveNumber);
return Player.sleeves[sleeveNumber].synchronize(Player);
},
setToCommitCrime: function (sleeveNumber: any = 0, crimeName: any = ""): any {
updateDynamicRam("setToCommitCrime", getRamCost("sleeve", "setToCommitCrime"));
checkSleeveAPIAccess("setToCommitCrime");
checkSleeveNumber("setToCommitCrime", sleeveNumber);
return Player.sleeves[sleeveNumber].commitCrime(Player, crimeName);
},
setToUniversityCourse: function (sleeveNumber: any = 0, universityName: any = "", className: any = ""): any {
updateDynamicRam("setToUniversityCourse", getRamCost("sleeve", "setToUniversityCourse"));
checkSleeveAPIAccess("setToUniversityCourse");
checkSleeveNumber("setToUniversityCourse", sleeveNumber);
return Player.sleeves[sleeveNumber].takeUniversityCourse(Player, universityName, className);
},
travel: function (sleeveNumber: any = 0, cityName: any = ""): any {
updateDynamicRam("travel", getRamCost("sleeve", "travel"));
checkSleeveAPIAccess("travel");
checkSleeveNumber("travel", sleeveNumber);
return Player.sleeves[sleeveNumber].travel(Player, cityName);
},
setToCompanyWork: function (sleeveNumber: any = 0, companyName: any = ""): any {
updateDynamicRam("setToCompanyWork", getRamCost("sleeve", "setToCompanyWork"));
checkSleeveAPIAccess("setToCompanyWork");
checkSleeveNumber("setToCompanyWork", sleeveNumber);
// Cannot work at the same company that another sleeve is working at stanek: stanek,
for (let i = 0; i < Player.sleeves.length; ++i) {
if (i === sleeveNumber) {
continue;
}
const other = Player.sleeves[i];
if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) {
throw makeRuntimeErrorMsg(
"sleeve.setToFactionWork",
`Sleeve ${sleeveNumber} cannot work for company ${companyName} because Sleeve ${i} is already working for them.`,
);
}
}
return Player.sleeves[sleeveNumber].workForCompany(Player, companyName);
},
setToFactionWork: function (sleeveNumber: any = 0, factionName: any = "", workType: any = ""): any {
updateDynamicRam("setToFactionWork", getRamCost("sleeve", "setToFactionWork"));
checkSleeveAPIAccess("setToFactionWork");
checkSleeveNumber("setToFactionWork", sleeveNumber);
// Cannot work at the same faction that another sleeve is working at
for (let i = 0; i < Player.sleeves.length; ++i) {
if (i === sleeveNumber) {
continue;
}
const other = Player.sleeves[i];
if (other.currentTask === SleeveTaskType.Faction && other.currentTaskLocation === factionName) {
throw makeRuntimeErrorMsg(
"sleeve.setToFactionWork",
`Sleeve ${sleeveNumber} cannot work for faction ${factionName} because Sleeve ${i} is already working for them.`,
);
}
}
return Player.sleeves[sleeveNumber].workForFaction(Player, factionName, workType);
},
setToGymWorkout: function (sleeveNumber: any = 0, gymName: any = "", stat: any = ""): any {
updateDynamicRam("setToGymWorkout", getRamCost("sleeve", "setToGymWorkout"));
checkSleeveAPIAccess("setToGymWorkout");
checkSleeveNumber("setToGymWorkout", sleeveNumber);
return Player.sleeves[sleeveNumber].workoutAtGym(Player, gymName, stat);
},
getSleeveStats: function (sleeveNumber: any = 0): any {
updateDynamicRam("getSleeveStats", getRamCost("sleeve", "getSleeveStats"));
checkSleeveAPIAccess("getSleeveStats");
checkSleeveNumber("getSleeveStats", sleeveNumber);
const sl = Player.sleeves[sleeveNumber];
return {
shock: 100 - sl.shock,
sync: sl.sync,
hacking_skill: sl.hacking_skill,
strength: sl.strength,
defense: sl.defense,
dexterity: sl.dexterity,
agility: sl.agility,
charisma: sl.charisma,
};
},
getTask: function (sleeveNumber: any = 0): any {
updateDynamicRam("getTask", getRamCost("sleeve", "getTask"));
checkSleeveAPIAccess("getTask");
checkSleeveNumber("getTask", sleeveNumber);
const sl = Player.sleeves[sleeveNumber];
return {
task: SleeveTaskType[sl.currentTask],
crime: sl.crimeType,
location: sl.currentTaskLocation,
gymStatType: sl.gymStatType,
factionWorkType: FactionWorkType[sl.factionWorkType],
};
},
getInformation: function (sleeveNumber: any = 0): any {
updateDynamicRam("getInformation", getRamCost("sleeve", "getInformation"));
checkSleeveAPIAccess("getInformation");
checkSleeveNumber("getInformation", sleeveNumber);
const sl = Player.sleeves[sleeveNumber];
return {
city: sl.city,
hp: sl.hp,
jobs: Object.keys(Player.jobs), // technically sleeves have the same jobs as the player.
jobTitle: Object.values(Player.jobs),
maxHp: sl.max_hp,
tor: SpecialServerIps.hasOwnProperty("Darkweb Server"), // There's no reason not to give that infomation here as well. Worst case scenario it isn't used.
mult: {
agility: sl.agility_mult,
agilityExp: sl.agility_exp_mult,
companyRep: sl.company_rep_mult,
crimeMoney: sl.crime_money_mult,
crimeSuccess: sl.crime_success_mult,
defense: sl.defense_mult,
defenseExp: sl.defense_exp_mult,
dexterity: sl.dexterity_mult,
dexterityExp: sl.dexterity_exp_mult,
factionRep: sl.faction_rep_mult,
hacking: sl.hacking_mult,
hackingExp: sl.hacking_exp_mult,
strength: sl.strength_mult,
strengthExp: sl.strength_exp_mult,
workMoney: sl.work_money_mult,
},
timeWorked: sl.currentTaskTime,
earningsForSleeves: {
workHackExpGain: sl.earningsForSleeves.hack,
workStrExpGain: sl.earningsForSleeves.str,
workDefExpGain: sl.earningsForSleeves.def,
workDexExpGain: sl.earningsForSleeves.dex,
workAgiExpGain: sl.earningsForSleeves.agi,
workChaExpGain: sl.earningsForSleeves.cha,
workMoneyGain: sl.earningsForSleeves.money,
},
earningsForPlayer: {
workHackExpGain: sl.earningsForPlayer.hack,
workStrExpGain: sl.earningsForPlayer.str,
workDefExpGain: sl.earningsForPlayer.def,
workDexExpGain: sl.earningsForPlayer.dex,
workAgiExpGain: sl.earningsForPlayer.agi,
workChaExpGain: sl.earningsForPlayer.cha,
workMoneyGain: sl.earningsForPlayer.money,
},
earningsForTask: {
workHackExpGain: sl.earningsForTask.hack,
workStrExpGain: sl.earningsForTask.str,
workDefExpGain: sl.earningsForTask.def,
workDexExpGain: sl.earningsForTask.dex,
workAgiExpGain: sl.earningsForTask.agi,
workChaExpGain: sl.earningsForTask.cha,
workMoneyGain: sl.earningsForTask.money,
},
workRepGain: sl.getRepGain(Player),
};
},
getSleeveAugmentations: function (sleeveNumber: any = 0): any {
updateDynamicRam("getSleeveAugmentations", getRamCost("sleeve", "getSleeveAugmentations"));
checkSleeveAPIAccess("getSleeveAugmentations");
checkSleeveNumber("getSleeveAugmentations", sleeveNumber);
const augs = [];
for (let i = 0; i < Player.sleeves[sleeveNumber].augmentations.length; i++) {
augs.push(Player.sleeves[sleeveNumber].augmentations[i].name);
}
return augs;
},
getSleevePurchasableAugs: function (sleeveNumber: any = 0): any {
updateDynamicRam("getSleevePurchasableAugs", getRamCost("sleeve", "getSleevePurchasableAugs"));
checkSleeveAPIAccess("getSleevePurchasableAugs");
checkSleeveNumber("getSleevePurchasableAugs", sleeveNumber);
const purchasableAugs = findSleevePurchasableAugs(Player.sleeves[sleeveNumber], Player);
const augs = [];
for (let i = 0; i < purchasableAugs.length; i++) {
const aug = purchasableAugs[i];
augs.push({
name: aug.name,
cost: aug.startingCost,
});
}
return augs;
},
purchaseSleeveAug: function (sleeveNumber: any = 0, augName: any = ""): any {
updateDynamicRam("purchaseSleeveAug", getRamCost("sleeve", "purchaseSleeveAug"));
checkSleeveAPIAccess("purchaseSleeveAug");
checkSleeveNumber("purchaseSleeveAug", sleeveNumber);
const aug = Augmentations[augName];
if (!aug) {
throw makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Invalid aug: ${augName}`);
}
return Player.sleeves[sleeveNumber].tryBuyAugmentation(Player, aug);
},
}, // End sleeve
// Stanek's gift API
stanek: {
charge: function (worldX: any, worldY: any): any {
updateDynamicRam("charge", getRamCost("stanek", "charge"));
//checkStanekAPIAccess("charge");
const fragment = staneksGift.fragmentAt(worldX, worldY);
if (!fragment) throw makeRuntimeErrorMsg("stanek.charge", `No fragment at (${worldX}, ${worldY})`);
return netscriptDelay(1000, workerScript).then(function () {
if (workerScript.env.stopFlag) {
return Promise.reject(workerScript);
}
const ram = workerScript.scriptRef.ramUsage * workerScript.scriptRef.threads;
return Promise.resolve(staneksGift.charge(worldX, worldY, ram));
});
},
fragmentDefinitions: function () {
updateDynamicRam("fragmentDefinitions", getRamCost("stanek", "fragmentDefinitions"));
//checkStanekAPIAccess("fragmentDefinitions");
return Fragments.map((f) => f.copy());
},
placedFragments: function () {
updateDynamicRam("placedFragments", getRamCost("stanek", "placedFragments"));
//checkStanekAPIAccess("placedFragments");
return staneksGift.fragments.map((af) => {
return { ...af.copy(), ...af.fragment().copy() };
});
},
clear: function () {
updateDynamicRam("clear", getRamCost("stanek", "clear"));
//checkStanekAPIAccess("clear");
staneksGift.clear();
},
canPlace: function (worldX: any, worldY: any, fragmentId: any): any {
updateDynamicRam("canPlace", getRamCost("stanek", "canPlace"));
//checkStanekAPIAccess("canPlace");
const fragment = FragmentById(fragmentId);
if (!fragment) throw makeRuntimeErrorMsg("stanek.canPlace", `Invalid fragment id: ${fragmentId}`);
return staneksGift.canPlace(worldX, worldY, fragment);
},
place: function (worldX: any, worldY: any, fragmentId: any): any {
updateDynamicRam("place", getRamCost("stanek", "place"));
//checkStanekAPIAccess("place");
const fragment = FragmentById(fragmentId);
if (!fragment) throw makeRuntimeErrorMsg("stanek.place", `Invalid fragment id: ${fragmentId}`);
return staneksGift.place(worldX, worldY, fragment);
},
fragmentAt: function (worldX: any, worldY: any): any {
updateDynamicRam("fragmentAt", getRamCost("stanek", "fragmentAt"));
//checkStanekAPIAccess("fragmentAt");
const fragment = staneksGift.fragmentAt(worldX, worldY);
if (fragment !== null) return fragment.copy();
return null;
},
deleteAt: function (worldX: any, worldY: any): any {
updateDynamicRam("deleteAt", getRamCost("stanek", "deleteAt"));
//checkStanekAPIAccess("deleteAt");
return staneksGift.deleteAt(worldX, worldY);
},
}, // End stanek
formulas: { formulas: {
basic: { basic: {
calculateSkill: function (exp: any, mult: any = 1): any { calculateSkill: function (exp: any, mult: any = 1): any {
@ -5328,28 +4614,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
}, },
}, },
}, // end formulas }, // end formulas
heart: {
// Easter egg function
break: function (): number {
return Player.karma;
},
},
exploit: function (): any {
Player.giveExploit(Exploit.UndocumentedFunctionCall);
},
bypass: function (doc: any): any {
// reset both fields first
doc.completely_unused_field = undefined;
const real_document: any = document;
real_document.completely_unused_field = undefined;
// set one to true and check that it affected the other.
real_document.completely_unused_field = true;
if (doc.completely_unused_field && workerScript.ramUsage === 1.6) {
Player.giveExploit(Exploit.Bypass);
}
doc.completely_unused_field = undefined;
real_document.completely_unused_field = undefined;
},
flags: function (data: any): any { flags: function (data: any): any {
data = toNative(data); data = toNative(data);
// We always want the help flag. // We always want the help flag.
@ -5382,6 +4646,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
} }
return ret; return ret;
}, },
...extra,
}; };
function getFunctionNames(obj: NS): string[] { function getFunctionNames(obj: NS): string[] {

@ -0,0 +1,39 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Exploit } from "../Exploits/Exploit";
export interface INetscriptExtra {
heart: {
break(): number;
};
exploit(): void;
bypass(doc: Document): void;
}
export function NetscriptExtra(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): INetscriptExtra {
return {
heart: {
// Easter egg function
break: function (): number {
return player.karma;
},
},
exploit: function (): void {
player.giveExploit(Exploit.UndocumentedFunctionCall);
},
bypass: function (doc: any): void {
// reset both fields first
doc.completely_unused_field = undefined;
const real_document: any = document;
real_document.completely_unused_field = undefined;
// set one to true and check that it affected the other.
real_document.completely_unused_field = true;
if (doc.completely_unused_field && workerScript.ramUsage === 1.6) {
player.giveExploit(Exploit.Bypass);
}
doc.completely_unused_field = undefined;
real_document.completely_unused_field = undefined;
},
};
}

@ -0,0 +1,314 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { Gang } from "../Gang/Gang";
import { AllGangs } from "../Gang/AllGangs";
import { GangMemberTasks } from "../Gang/GangMemberTasks";
import { GangMemberUpgrades } from "../Gang/GangMemberUpgrades";
import { WorkerScript } from "../Netscript/WorkerScript";
import { GangMember } from "../Gang/GangMember";
import { GangMemberTask } from "../Gang/GangMemberTask";
export interface INetscriptGang {
createGang(faction: string): boolean;
inGang(): boolean;
getMemberNames(): string[];
getGangInformation(): any;
getOtherGangInformation(): any;
getMemberInformation(name: string): any;
canRecruitMember(): boolean;
recruitMember(name: string): boolean;
getTaskNames(): string[];
setMemberTask(memberName: string, taskName: string): boolean;
getTaskStats(taskName: string): any;
getEquipmentNames(): string[];
getEquipmentCost(equipName: string): number;
getEquipmentType(equipName: string): string;
getEquipmentStats(equipName: string): any;
purchaseEquipment(memberName: string, equipName: string): any;
ascendMember(name: string): any;
setTerritoryWarfare(engage: boolean): void;
getChanceToWinClash(otherGang: string): number;
getBonusTime(): number;
}
export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): INetscriptGang {
const checkGangApiAccess = function (func: string): void {
const gang = player.gang;
if (gang === null) throw new Error("Must have joined gang");
const hasAccess = gang instanceof Gang;
if (!hasAccess) {
throw helper.makeRuntimeErrorMsg(`gang.${func}`, `You do not currently have a Gang`);
}
};
const getGangMember = function (func: string, name: string): GangMember {
const gang = player.gang;
if (gang === null) throw new Error("Must have joined gang");
for (const member of gang.members) if (member.name === name) return member;
throw helper.makeRuntimeErrorMsg(`gang.${func}`, `Invalid gang member: '${name}'`);
};
const getGangTask = function (func: string, name: string): GangMemberTask {
const task = GangMemberTasks[name];
if (!task) {
throw helper.makeRuntimeErrorMsg(`gang.${func}`, `Invalid task: '${name}'`);
}
return task;
};
return {
createGang: function (faction: string): boolean {
helper.updateDynamicRam("createGang", getRamCost("gang", "createGang"));
// this list is copied from Faction/ui/Root.tsx
const GangNames = [
"Slum Snakes",
"Tetrads",
"The Syndicate",
"The Dark Army",
"Speakers for the Dead",
"NiteSec",
"The Black Hand",
];
if (!player.canAccessGang() || !GangNames.includes(faction)) return false;
if (player.inGang()) return false;
if (!player.factions.includes(faction)) return false;
const isHacking = faction === "NiteSec" || faction === "The Black Hand";
player.startGang(faction, isHacking);
return true;
},
inGang: function (): any {
helper.updateDynamicRam("inGang", getRamCost("gang", "inGang"));
return player.inGang();
},
getMemberNames: function (): any {
helper.updateDynamicRam("getMemberNames", getRamCost("gang", "getMemberNames"));
checkGangApiAccess("getMemberNames");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return gang.members.map((member) => member.name);
},
getGangInformation: function (): any {
helper.updateDynamicRam("getGangInformation", getRamCost("gang", "getGangInformation"));
checkGangApiAccess("getGangInformation");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return {
faction: gang.facName,
isHacking: gang.isHackingGang,
moneyGainRate: gang.moneyGainRate,
power: gang.getPower(),
respect: gang.respect,
respectGainRate: gang.respectGainRate,
territory: gang.getTerritory(),
territoryClashChance: gang.territoryClashChance,
territoryWarfareEngaged: gang.territoryWarfareEngaged,
wantedLevel: gang.wanted,
wantedLevelGainRate: gang.wantedGainRate,
};
},
getOtherGangInformation: function (): any {
helper.updateDynamicRam("getOtherGangInformation", getRamCost("gang", "getOtherGangInformation"));
checkGangApiAccess("getOtherGangInformation");
const cpy: any = {};
for (const gang in AllGangs) {
cpy[gang] = Object.assign({}, AllGangs[gang]);
}
return cpy;
},
getMemberInformation: function (name: any): any {
helper.updateDynamicRam("getMemberInformation", getRamCost("gang", "getMemberInformation"));
checkGangApiAccess("getMemberInformation");
const member = getGangMember("getMemberInformation", name);
return {
name: member.name,
task: member.task,
earnedRespect: member.earnedRespect,
hack: member.hack,
str: member.str,
def: member.def,
dex: member.dex,
agi: member.agi,
cha: member.cha,
hack_exp: member.hack_exp,
str_exp: member.str_exp,
def_exp: member.def_exp,
dex_exp: member.dex_exp,
agi_exp: member.agi_exp,
cha_exp: member.cha_exp,
hack_mult: member.hack_mult,
str_mult: member.str_mult,
def_mult: member.def_mult,
dex_mult: member.dex_mult,
agi_mult: member.agi_mult,
cha_mult: member.cha_mult,
hack_asc_mult: member.calculateAscensionMult(member.hack_asc_points),
str_asc_mult: member.calculateAscensionMult(member.str_asc_points),
def_asc_mult: member.calculateAscensionMult(member.def_asc_points),
dex_asc_mult: member.calculateAscensionMult(member.dex_asc_points),
agi_asc_mult: member.calculateAscensionMult(member.agi_asc_points),
cha_asc_mult: member.calculateAscensionMult(member.cha_asc_points),
hack_asc_points: member.hack_asc_points,
str_asc_points: member.str_asc_points,
def_asc_points: member.def_asc_points,
dex_asc_points: member.dex_asc_points,
agi_asc_points: member.agi_asc_points,
cha_asc_points: member.cha_asc_points,
upgrades: member.upgrades.slice(),
augmentations: member.augmentations.slice(),
};
},
canRecruitMember: function (): any {
helper.updateDynamicRam("canRecruitMember", getRamCost("gang", "canRecruitMember"));
checkGangApiAccess("canRecruitMember");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return gang.canRecruitMember();
},
recruitMember: function (name: any): any {
helper.updateDynamicRam("recruitMember", getRamCost("gang", "recruitMember"));
checkGangApiAccess("recruitMember");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const recruited = gang.recruitMember(name);
if (recruited) {
workerScript.log("recruitMember", `Successfully recruited Gang Member '${name}'`);
} else {
workerScript.log("recruitMember", `Failed to recruit Gang Member '${name}'`);
}
return recruited;
},
getTaskNames: function (): any {
helper.updateDynamicRam("getTaskNames", getRamCost("gang", "getTaskNames"));
checkGangApiAccess("getTaskNames");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const tasks = gang.getAllTaskNames();
tasks.unshift("Unassigned");
return tasks;
},
setMemberTask: function (memberName: any, taskName: any): any {
helper.updateDynamicRam("setMemberTask", getRamCost("gang", "setMemberTask"));
checkGangApiAccess("setMemberTask");
const member = getGangMember("setMemberTask", memberName);
const success = member.assignToTask(taskName);
if (success) {
workerScript.log("setMemberTask", `Successfully assigned Gang Member '${memberName}' to '${taskName}' task`);
} else {
workerScript.log(
"setMemberTask",
`Failed to assign Gang Member '${memberName}' to '${taskName}' task. '${memberName}' is now Unassigned`,
);
}
return success;
},
getTaskStats: function (taskName: any): any {
helper.updateDynamicRam("getTaskStats", getRamCost("gang", "getTaskStats"));
checkGangApiAccess("getTaskStats");
const task = getGangTask("getTaskStats", taskName);
const copy = Object.assign({}, task);
copy.territory = Object.assign({}, task.territory);
return copy;
},
getEquipmentNames: function (): any {
helper.updateDynamicRam("getEquipmentNames", getRamCost("gang", "getEquipmentNames"));
checkGangApiAccess("getEquipmentNames");
return Object.keys(GangMemberUpgrades);
},
getEquipmentCost: function (equipName: any): any {
helper.updateDynamicRam("getEquipmentCost", getRamCost("gang", "getEquipmentCost"));
checkGangApiAccess("getEquipmentCost");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const upg = GangMemberUpgrades[equipName];
if (upg === null) return Infinity;
return gang.getUpgradeCost(upg);
},
getEquipmentType: function (equipName: any): any {
helper.updateDynamicRam("getEquipmentType", getRamCost("gang", "getEquipmentType"));
checkGangApiAccess("getEquipmentType");
const upg = GangMemberUpgrades[equipName];
if (upg == null) return "";
return upg.getType();
},
getEquipmentStats: function (equipName: any): any {
helper.updateDynamicRam("getEquipmentStats", getRamCost("gang", "getEquipmentStats"));
checkGangApiAccess("getEquipmentStats");
const equipment = GangMemberUpgrades[equipName];
if (!equipment) {
throw helper.makeRuntimeErrorMsg("getEquipmentStats", `Invalid equipment: ${equipName}`);
}
return Object.assign({}, equipment.mults);
},
purchaseEquipment: function (memberName: any, equipName: any): any {
helper.updateDynamicRam("purchaseEquipment", getRamCost("gang", "purchaseEquipment"));
checkGangApiAccess("purchaseEquipment");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("purchaseEquipment", memberName);
const equipment = GangMemberUpgrades[equipName];
if (!equipment) return false;
const res = member.buyUpgrade(equipment, player, gang);
if (res) {
workerScript.log("purchaseEquipment", `Purchased '${equipName}' for Gang member '${memberName}'`);
} else {
workerScript.log("purchaseEquipment", `Failed to purchase '${equipName}' for Gang member '${memberName}'`);
}
return res;
},
ascendMember: function (name: any): any {
helper.updateDynamicRam("ascendMember", getRamCost("gang", "ascendMember"));
checkGangApiAccess("ascendMember");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("ascendMember", name);
if (!member.canAscend()) return;
return gang.ascendMember(member, workerScript);
},
setTerritoryWarfare: function (engage: any): void {
helper.updateDynamicRam("setTerritoryWarfare", getRamCost("gang", "setTerritoryWarfare"));
checkGangApiAccess("setTerritoryWarfare");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (engage) {
gang.territoryWarfareEngaged = true;
workerScript.log("setTerritoryWarfare", "Engaging in Gang Territory Warfare");
} else {
gang.territoryWarfareEngaged = false;
workerScript.log("setTerritoryWarfare", "Disengaging in Gang Territory Warfare");
}
},
getChanceToWinClash: function (otherGang: any): any {
helper.updateDynamicRam("getChanceToWinClash", getRamCost("gang", "getChanceToWinClash"));
checkGangApiAccess("getChanceToWinClash");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (AllGangs[otherGang] == null) {
throw helper.makeRuntimeErrorMsg(`gang.getChanceToWinClash`, `Invalid gang: ${otherGang}`);
}
const playerPower = AllGangs[gang.facName].power;
const otherPower = AllGangs[otherGang].power;
return playerPower / (otherPower + playerPower);
},
getBonusTime: function (): any {
helper.updateDynamicRam("getBonusTime", getRamCost("gang", "getBonusTime"));
checkGangApiAccess("getBonusTime");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return Math.round(gang.storedCycles / 5);
},
};
}

@ -0,0 +1,213 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer";
import { WorkerScript } from "../Netscript/WorkerScript";
import { HacknetServerConstants } from "../Hacknet/data/Constants";
import {
getCostOfNextHacknetNode,
getCostOfNextHacknetServer,
hasHacknetServers,
purchaseHacknet,
purchaseLevelUpgrade,
purchaseRamUpgrade,
purchaseCoreUpgrade,
purchaseCacheUpgrade,
purchaseHashUpgrade,
updateHashManagerCapacity,
} from "../Hacknet/HacknetHelpers";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { HacknetNode } from "../Hacknet/HacknetNode";
import { AllServers } from "../Server/AllServers";
export interface INetscriptHacknet {
numNodes(): number;
maxNumNodes(): number;
purchaseNode(): any;
getPurchaseNodeCost(): number;
getNodeStats(i: number): any;
upgradeLevel(i: number, n: number): boolean;
upgradeRam(i: number, n: number): boolean;
upgradeCore(i: number, n: number): boolean;
upgradeCache(i: number, n: number): boolean;
getLevelUpgradeCost(i: number, n: number): number;
getRamUpgradeCost(i: number, n: number): number;
getCoreUpgradeCost(i: number, n: number): number;
getCacheUpgradeCost(i: number, n: number): number;
numHashes(): number;
hashCapacity(): number;
hashCost(upgName: string): number;
spendHashes(upgName: string, upgTarget: string): any;
getHashUpgradeLevel(upgName: string): number;
getStudyMult(): number;
getTrainingMult(): number;
}
export function NetscriptHacknet(
player: IPlayer,
workerScript: WorkerScript,
helper: INetscriptHelper,
): INetscriptHacknet {
// Utility function to get Hacknet Node object
const getHacknetNode = function (i: any, callingFn = ""): HacknetNode | HacknetServer {
if (isNaN(i)) {
throw helper.makeRuntimeErrorMsg(callingFn, "Invalid index specified for Hacknet Node: " + i);
}
if (i < 0 || i >= player.hacknetNodes.length) {
throw helper.makeRuntimeErrorMsg(callingFn, "Index specified for Hacknet Node is out-of-bounds: " + i);
}
if (hasHacknetServers(player)) {
const hi = player.hacknetNodes[i];
if (typeof hi !== "string") throw new Error("hacknet node was not a string");
const hserver = AllServers[hi];
if (!(hserver instanceof HacknetServer)) throw new Error("hacknet server was not actually hacknet server");
if (hserver == null) {
throw helper.makeRuntimeErrorMsg(
callingFn,
`Could not get Hacknet Server for index ${i}. This is probably a bug, please report to game dev`,
);
}
return hserver;
} else {
const node = player.hacknetNodes[i];
if (!(node instanceof HacknetNode)) throw new Error("hacknet node was not node.");
return node;
}
};
return {
numNodes: function (): any {
return player.hacknetNodes.length;
},
maxNumNodes: function (): any {
if (hasHacknetServers(player)) {
return HacknetServerConstants.MaxServers;
}
return Infinity;
},
purchaseNode: function (): any {
return purchaseHacknet(player);
},
getPurchaseNodeCost: function (): any {
if (hasHacknetServers(player)) {
return getCostOfNextHacknetServer(player);
} else {
return getCostOfNextHacknetNode(player);
}
},
getNodeStats: function (i: any): any {
const node = getHacknetNode(i, "getNodeStats");
const hasUpgraded = hasHacknetServers(player);
const res: any = {
name: node instanceof HacknetServer ? node.hostname : node.name,
level: node.level,
ram: node instanceof HacknetServer ? node.maxRam : node.ram,
cores: node.cores,
production: node instanceof HacknetServer ? node.hashRate : node.moneyGainRatePerSecond,
timeOnline: node.onlineTimeSeconds,
totalProduction: node instanceof HacknetServer ? node.totalHashesGenerated : node.totalMoneyGenerated,
};
if (hasUpgraded && node instanceof HacknetServer) {
res.cache = node.cache;
res.hashCapacity = node.hashCapacity;
}
return res;
},
upgradeLevel: function (i: any, n: any): any {
const node = getHacknetNode(i, "upgradeLevel");
return purchaseLevelUpgrade(player, node, n);
},
upgradeRam: function (i: any, n: any): any {
const node = getHacknetNode(i, "upgradeRam");
return purchaseRamUpgrade(player, node, n);
},
upgradeCore: function (i: any, n: any): any {
const node = getHacknetNode(i, "upgradeCore");
return purchaseCoreUpgrade(player, node, n);
},
upgradeCache: function (i: any, n: any): any {
if (!hasHacknetServers(player)) {
return false;
}
const node = getHacknetNode(i, "upgradeCache");
if (!(node instanceof HacknetServer)) {
workerScript.log("upgradeCache", "Can only be called on hacknet servers");
return false;
}
const res = purchaseCacheUpgrade(player, node, n);
if (res) {
updateHashManagerCapacity(player);
}
return res;
},
getLevelUpgradeCost: function (i: any, n: any): any {
const node = getHacknetNode(i, "upgradeLevel");
return node.calculateLevelUpgradeCost(n, player.hacknet_node_level_cost_mult);
},
getRamUpgradeCost: function (i: any, n: any): any {
const node = getHacknetNode(i, "upgradeRam");
return node.calculateRamUpgradeCost(n, player.hacknet_node_ram_cost_mult);
},
getCoreUpgradeCost: function (i: any, n: any): any {
const node = getHacknetNode(i, "upgradeCore");
return node.calculateCoreUpgradeCost(n, player.hacknet_node_core_cost_mult);
},
getCacheUpgradeCost: function (i: any, n: any): any {
if (!hasHacknetServers(player)) {
return Infinity;
}
const node = getHacknetNode(i, "upgradeCache");
if (!(node instanceof HacknetServer)) {
workerScript.log("getCacheUpgradeCost", "Can only be called on hacknet servers");
return -1;
}
return node.calculateCacheUpgradeCost(n);
},
numHashes: function (): any {
if (!hasHacknetServers(player)) {
return 0;
}
return player.hashManager.hashes;
},
hashCapacity: function (): any {
if (!hasHacknetServers(player)) {
return 0;
}
return player.hashManager.capacity;
},
hashCost: function (upgName: any): any {
if (!hasHacknetServers(player)) {
return Infinity;
}
return player.hashManager.getUpgradeCost(upgName);
},
spendHashes: function (upgName: any, upgTarget: any): any {
if (!hasHacknetServers(player)) {
return false;
}
return purchaseHashUpgrade(player, upgName, upgTarget);
},
getHashUpgradeLevel: function (upgName: any): any {
const level = player.hashManager.upgrades[upgName];
if (level === undefined) {
throw helper.makeRuntimeErrorMsg("hacknet.hashUpgradeLevel", `Invalid Hash Upgrade: ${upgName}`);
}
return level;
},
getStudyMult: function (): any {
if (!hasHacknetServers(player)) {
return false;
}
return player.hashManager.getStudyMult();
},
getTrainingMult: function (): any {
if (!hasHacknetServers(player)) {
return false;
}
return player.hashManager.getTrainingMult();
},
};
}

@ -0,0 +1,7 @@
export interface INetscriptHelper {
updateDynamicRam(functionName: string, ram: number): void;
makeRuntimeErrorMsg(functionName: string, message: string): void;
string(funcName: string, argName: string, v: any): string;
number(funcName: string, argName: string, v: any): number;
boolean(v: any): boolean;
}

@ -0,0 +1,333 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { FactionWorkType } from "../Faction/FactionWorkTypeEnum";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { SleeveTaskType } from "../PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { SpecialServerIps } from "../Server/SpecialServerIps";
import { WorkerScript } from "../Netscript/WorkerScript";
import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers";
import { Augmentations } from "../Augmentation/Augmentations";
import { CityName } from "../Locations/data/CityNames";
export interface INetscriptSleeve {
getNumSleeves(): number;
setToShockRecovery(sleeveNumber?: number): boolean;
setToSynchronize(sleeveNumber?: number): boolean;
setToCommitCrime(sleeveNumber?: number, crimeName?: string): boolean;
setToUniversityCourse(sleeveNumber?: number, universityName?: string, className?: string): boolean;
travel(sleeveNumber?: number, cityName?: string): boolean;
setToCompanyWork(sleeveNumber?: number, companyName?: string): boolean;
setToFactionWork(sleeveNumber?: number, factionName?: string, workType?: string): boolean;
setToGymWorkout(sleeveNumber?: number, gymName?: string, stat?: string): boolean;
getSleeveStats(sleeveNumber?: number): {
shock: number;
sync: number;
hacking_skill: number;
strength: number;
defense: number;
dexterity: number;
agility: number;
charisma: number;
};
getTask(sleeveNumber?: number): {
task: string;
crime: string;
location: string;
gymStatType: string;
factionWorkType: string;
};
getInformation(sleeveNumber?: number): any;
getSleeveAugmentations(sleeveNumber?: number): string[];
getSleevePurchasableAugs(sleeveNumber?: number): {
name: string;
cost: number;
}[];
purchaseSleeveAug(sleeveNumber?: number, augName?: string): boolean;
}
export function NetscriptSleeve(
player: IPlayer,
workerScript: WorkerScript,
helper: INetscriptHelper,
): INetscriptSleeve {
const checkSleeveAPIAccess = function (func: any): void {
if (player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw helper.makeRuntimeErrorMsg(
`sleeve.${func}`,
"You do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10",
);
}
};
const checkSleeveNumber = function (func: any, sleeveNumber: any): void {
if (sleeveNumber >= player.sleeves.length || sleeveNumber < 0) {
const msg = `Invalid sleeve number: ${sleeveNumber}`;
workerScript.log(func, msg);
throw helper.makeRuntimeErrorMsg(`sleeve.${func}`, msg);
}
};
return {
getNumSleeves: function (): number {
helper.updateDynamicRam("getNumSleeves", getRamCost("sleeve", "getNumSleeves"));
checkSleeveAPIAccess("getNumSleeves");
return player.sleeves.length;
},
setToShockRecovery: function (asleeveNumber: any = 0): boolean {
const sleeveNumber = helper.number("setToShockRecovery", "sleeveNumber", asleeveNumber);
helper.updateDynamicRam("setToShockRecovery", getRamCost("sleeve", "setToShockRecovery"));
checkSleeveAPIAccess("setToShockRecovery");
checkSleeveNumber("setToShockRecovery", sleeveNumber);
return player.sleeves[sleeveNumber].shockRecovery(player);
},
setToSynchronize: function (asleeveNumber: any = 0): boolean {
const sleeveNumber = helper.number("setToSynchronize", "sleeveNumber", asleeveNumber);
helper.updateDynamicRam("setToSynchronize", getRamCost("sleeve", "setToSynchronize"));
checkSleeveAPIAccess("setToSynchronize");
checkSleeveNumber("setToSynchronize", sleeveNumber);
return player.sleeves[sleeveNumber].synchronize(player);
},
setToCommitCrime: function (asleeveNumber: any = 0, acrimeName: any = ""): boolean {
const sleeveNumber = helper.number("setToCommitCrime", "sleeveNumber", asleeveNumber);
const crimeName = helper.string("setToUniversityCourse", "crimeName", acrimeName);
helper.updateDynamicRam("setToCommitCrime", getRamCost("sleeve", "setToCommitCrime"));
checkSleeveAPIAccess("setToCommitCrime");
checkSleeveNumber("setToCommitCrime", sleeveNumber);
return player.sleeves[sleeveNumber].commitCrime(player, crimeName);
},
setToUniversityCourse: function (asleeveNumber: any = 0, auniversityName: any = "", aclassName: any = ""): boolean {
const sleeveNumber = helper.number("setToUniversityCourse", "sleeveNumber", asleeveNumber);
const universityName = helper.string("setToUniversityCourse", "universityName", auniversityName);
const className = helper.string("setToUniversityCourse", "className", aclassName);
helper.updateDynamicRam("setToUniversityCourse", getRamCost("sleeve", "setToUniversityCourse"));
checkSleeveAPIAccess("setToUniversityCourse");
checkSleeveNumber("setToUniversityCourse", sleeveNumber);
return player.sleeves[sleeveNumber].takeUniversityCourse(player, universityName, className);
},
travel: function (asleeveNumber: any = 0, acityName: any = ""): boolean {
const sleeveNumber = helper.number("travel", "sleeveNumber", asleeveNumber);
const cityName = helper.string("setToUniversityCourse", "cityName", acityName);
helper.updateDynamicRam("travel", getRamCost("sleeve", "travel"));
checkSleeveAPIAccess("travel");
checkSleeveNumber("travel", sleeveNumber);
return player.sleeves[sleeveNumber].travel(player, cityName as CityName);
},
setToCompanyWork: function (asleeveNumber: any = 0, acompanyName: any = ""): boolean {
const sleeveNumber = helper.number("setToCompanyWork", "sleeveNumber", asleeveNumber);
const companyName = helper.string("setToUniversityCourse", "companyName", acompanyName);
helper.updateDynamicRam("setToCompanyWork", getRamCost("sleeve", "setToCompanyWork"));
checkSleeveAPIAccess("setToCompanyWork");
checkSleeveNumber("setToCompanyWork", sleeveNumber);
// Cannot work at the same company that another sleeve is working at
for (let i = 0; i < player.sleeves.length; ++i) {
if (i === sleeveNumber) {
continue;
}
const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) {
throw helper.makeRuntimeErrorMsg(
"sleeve.setToFactionWork",
`Sleeve ${sleeveNumber} cannot work for company ${companyName} because Sleeve ${i} is already working for them.`,
);
}
}
return player.sleeves[sleeveNumber].workForCompany(player, companyName);
},
setToFactionWork: function (asleeveNumber: any = 0, afactionName: any = "", aworkType: any = ""): boolean {
const sleeveNumber = helper.number("setToFactionWork", "sleeveNumber", asleeveNumber);
const factionName = helper.string("setToUniversityCourse", "factionName", afactionName);
const workType = helper.string("setToUniversityCourse", "workType", aworkType);
helper.updateDynamicRam("setToFactionWork", getRamCost("sleeve", "setToFactionWork"));
checkSleeveAPIAccess("setToFactionWork");
checkSleeveNumber("setToFactionWork", sleeveNumber);
// Cannot work at the same faction that another sleeve is working at
for (let i = 0; i < player.sleeves.length; ++i) {
if (i === sleeveNumber) {
continue;
}
const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Faction && other.currentTaskLocation === factionName) {
throw helper.makeRuntimeErrorMsg(
"sleeve.setToFactionWork",
`Sleeve ${sleeveNumber} cannot work for faction ${factionName} because Sleeve ${i} is already working for them.`,
);
}
}
return player.sleeves[sleeveNumber].workForFaction(player, factionName, workType);
},
setToGymWorkout: function (asleeveNumber: any = 0, agymName: any = "", astat: any = ""): boolean {
const sleeveNumber = helper.number("setToGymWorkout", "sleeveNumber", asleeveNumber);
const gymName = helper.string("setToUniversityCourse", "gymName", agymName);
const stat = helper.string("setToUniversityCourse", "stat", astat);
helper.updateDynamicRam("setToGymWorkout", getRamCost("sleeve", "setToGymWorkout"));
checkSleeveAPIAccess("setToGymWorkout");
checkSleeveNumber("setToGymWorkout", sleeveNumber);
return player.sleeves[sleeveNumber].workoutAtGym(player, gymName, stat);
},
getSleeveStats: function (asleeveNumber: any = 0): {
shock: number;
sync: number;
hacking_skill: number;
strength: number;
defense: number;
dexterity: number;
agility: number;
charisma: number;
} {
const sleeveNumber = helper.number("getSleeveStats", "sleeveNumber", asleeveNumber);
helper.updateDynamicRam("getSleeveStats", getRamCost("sleeve", "getSleeveStats"));
checkSleeveAPIAccess("getSleeveStats");
checkSleeveNumber("getSleeveStats", sleeveNumber);
const sl = player.sleeves[sleeveNumber];
return {
shock: 100 - sl.shock,
sync: sl.sync,
hacking_skill: sl.hacking_skill,
strength: sl.strength,
defense: sl.defense,
dexterity: sl.dexterity,
agility: sl.agility,
charisma: sl.charisma,
};
},
getTask: function (asleeveNumber: any = 0): {
task: string;
crime: string;
location: string;
gymStatType: string;
factionWorkType: string;
} {
const sleeveNumber = helper.number("getTask", "sleeveNumber", asleeveNumber);
helper.updateDynamicRam("getTask", getRamCost("sleeve", "getTask"));
checkSleeveAPIAccess("getTask");
checkSleeveNumber("getTask", sleeveNumber);
const sl = player.sleeves[sleeveNumber];
return {
task: SleeveTaskType[sl.currentTask],
crime: sl.crimeType,
location: sl.currentTaskLocation,
gymStatType: sl.gymStatType,
factionWorkType: FactionWorkType[sl.factionWorkType],
};
},
getInformation: function (asleeveNumber: any = 0): any {
const sleeveNumber = helper.number("getInformation", "sleeveNumber", asleeveNumber);
helper.updateDynamicRam("getInformation", getRamCost("sleeve", "getInformation"));
checkSleeveAPIAccess("getInformation");
checkSleeveNumber("getInformation", sleeveNumber);
const sl = player.sleeves[sleeveNumber];
return {
city: sl.city,
hp: sl.hp,
jobs: Object.keys(player.jobs), // technically sleeves have the same jobs as the player.
jobTitle: Object.values(player.jobs),
maxHp: sl.max_hp,
tor: SpecialServerIps.hasOwnProperty("Darkweb Server"), // There's no reason not to give that infomation here as well. Worst case scenario it isn't used.
mult: {
agility: sl.agility_mult,
agilityExp: sl.agility_exp_mult,
companyRep: sl.company_rep_mult,
crimeMoney: sl.crime_money_mult,
crimeSuccess: sl.crime_success_mult,
defense: sl.defense_mult,
defenseExp: sl.defense_exp_mult,
dexterity: sl.dexterity_mult,
dexterityExp: sl.dexterity_exp_mult,
factionRep: sl.faction_rep_mult,
hacking: sl.hacking_mult,
hackingExp: sl.hacking_exp_mult,
strength: sl.strength_mult,
strengthExp: sl.strength_exp_mult,
workMoney: sl.work_money_mult,
},
timeWorked: sl.currentTaskTime,
earningsForSleeves: {
workHackExpGain: sl.earningsForSleeves.hack,
workStrExpGain: sl.earningsForSleeves.str,
workDefExpGain: sl.earningsForSleeves.def,
workDexExpGain: sl.earningsForSleeves.dex,
workAgiExpGain: sl.earningsForSleeves.agi,
workChaExpGain: sl.earningsForSleeves.cha,
workMoneyGain: sl.earningsForSleeves.money,
},
earningsForPlayer: {
workHackExpGain: sl.earningsForPlayer.hack,
workStrExpGain: sl.earningsForPlayer.str,
workDefExpGain: sl.earningsForPlayer.def,
workDexExpGain: sl.earningsForPlayer.dex,
workAgiExpGain: sl.earningsForPlayer.agi,
workChaExpGain: sl.earningsForPlayer.cha,
workMoneyGain: sl.earningsForPlayer.money,
},
earningsForTask: {
workHackExpGain: sl.earningsForTask.hack,
workStrExpGain: sl.earningsForTask.str,
workDefExpGain: sl.earningsForTask.def,
workDexExpGain: sl.earningsForTask.dex,
workAgiExpGain: sl.earningsForTask.agi,
workChaExpGain: sl.earningsForTask.cha,
workMoneyGain: sl.earningsForTask.money,
},
workRepGain: sl.getRepGain(player),
};
},
getSleeveAugmentations: function (asleeveNumber: any = 0): string[] {
const sleeveNumber = helper.number("getSleeveAugmentations", "sleeveNumber", asleeveNumber);
helper.updateDynamicRam("getSleeveAugmentations", getRamCost("sleeve", "getSleeveAugmentations"));
checkSleeveAPIAccess("getSleeveAugmentations");
checkSleeveNumber("getSleeveAugmentations", sleeveNumber);
const augs = [];
for (let i = 0; i < player.sleeves[sleeveNumber].augmentations.length; i++) {
augs.push(player.sleeves[sleeveNumber].augmentations[i].name);
}
return augs;
},
getSleevePurchasableAugs: function (asleeveNumber: any = 0): {
name: string;
cost: number;
}[] {
const sleeveNumber = helper.number("getSleevePurchasableAugs", "sleeveNumber", asleeveNumber);
helper.updateDynamicRam("getSleevePurchasableAugs", getRamCost("sleeve", "getSleevePurchasableAugs"));
checkSleeveAPIAccess("getSleevePurchasableAugs");
checkSleeveNumber("getSleevePurchasableAugs", sleeveNumber);
const purchasableAugs = findSleevePurchasableAugs(player.sleeves[sleeveNumber], player);
const augs = [];
for (let i = 0; i < purchasableAugs.length; i++) {
const aug = purchasableAugs[i];
augs.push({
name: aug.name,
cost: aug.startingCost,
});
}
return augs;
},
purchaseSleeveAug: function (asleeveNumber: any = 0, aaugName: any = ""): boolean {
const sleeveNumber = helper.number("purchaseSleeveAug", "sleeveNumber", asleeveNumber);
const augName = helper.string("setToUniversityCourse", "augName", aaugName);
helper.updateDynamicRam("purchaseSleeveAug", getRamCost("sleeve", "purchaseSleeveAug"));
checkSleeveAPIAccess("purchaseSleeveAug");
checkSleeveNumber("purchaseSleeveAug", sleeveNumber);
const aug = Augmentations[augName];
if (!aug) {
throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Invalid aug: ${augName}`);
}
return player.sleeves[sleeveNumber].tryBuyAugmentation(player, aug);
},
};
}

@ -0,0 +1,84 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer";
import { WorkerScript } from "../Netscript/WorkerScript";
import { netscriptDelay } from "../NetscriptEvaluator";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { staneksGift } from "../CotMG/Helper";
import { Fragments, FragmentById } from "../CotMG/Fragment";
export interface INetscriptStanek {
charge(worldX: any, worldY: any): any;
fragmentDefinitions(): any;
placedFragments(): any;
clear(): any;
canPlace(worldX: any, worldY: any, fragmentId: any): any;
place(worldX: any, worldY: any, fragmentId: any): any;
fragmentAt(worldX: any, worldY: any): any;
deleteAt(worldX: any, worldY: any): any;
}
export function NetscriptStanek(
player: IPlayer,
workerScript: WorkerScript,
helper: INetscriptHelper,
): INetscriptStanek {
return {
charge: function (worldX: any, worldY: any): any {
helper.updateDynamicRam("charge", getRamCost("stanek", "charge"));
//checkStanekAPIAccess("charge");
const fragment = staneksGift.fragmentAt(worldX, worldY);
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.charge", `No fragment at (${worldX}, ${worldY})`);
return netscriptDelay(1000, workerScript).then(function () {
if (workerScript.env.stopFlag) {
return Promise.reject(workerScript);
}
const ram = workerScript.scriptRef.ramUsage * workerScript.scriptRef.threads;
return Promise.resolve(staneksGift.charge(worldX, worldY, ram));
});
},
fragmentDefinitions: function () {
helper.updateDynamicRam("fragmentDefinitions", getRamCost("stanek", "fragmentDefinitions"));
//checkStanekAPIAccess("fragmentDefinitions");
return Fragments.map((f) => f.copy());
},
placedFragments: function () {
helper.updateDynamicRam("placedFragments", getRamCost("stanek", "placedFragments"));
//checkStanekAPIAccess("placedFragments");
return staneksGift.fragments.map((af) => {
return { ...af.copy(), ...af.fragment().copy() };
});
},
clear: function () {
helper.updateDynamicRam("clear", getRamCost("stanek", "clear"));
//checkStanekAPIAccess("clear");
staneksGift.clear();
},
canPlace: function (worldX: any, worldY: any, fragmentId: any): any {
helper.updateDynamicRam("canPlace", getRamCost("stanek", "canPlace"));
//checkStanekAPIAccess("canPlace");
const fragment = FragmentById(fragmentId);
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.canPlace", `Invalid fragment id: ${fragmentId}`);
return staneksGift.canPlace(worldX, worldY, fragment);
},
place: function (worldX: any, worldY: any, fragmentId: any): any {
helper.updateDynamicRam("place", getRamCost("stanek", "place"));
//checkStanekAPIAccess("place");
const fragment = FragmentById(fragmentId);
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.place", `Invalid fragment id: ${fragmentId}`);
return staneksGift.place(worldX, worldY, fragment);
},
fragmentAt: function (worldX: any, worldY: any): any {
helper.updateDynamicRam("fragmentAt", getRamCost("stanek", "fragmentAt"));
//checkStanekAPIAccess("fragmentAt");
const fragment = staneksGift.fragmentAt(worldX, worldY);
if (fragment !== null) return fragment.copy();
return null;
},
deleteAt: function (worldX: any, worldY: any): any {
helper.updateDynamicRam("deleteAt", getRamCost("stanek", "deleteAt"));
//checkStanekAPIAccess("deleteAt");
return staneksGift.deleteAt(worldX, worldY);
},
};
}

@ -21,7 +21,6 @@ import { Script } from "./Script/Script";
import { AllServers } from "./Server/AllServers"; import { AllServers } from "./Server/AllServers";
import { BaseServer } from "./Server/BaseServer"; import { BaseServer } from "./Server/BaseServer";
import { Settings } from "./Settings/Settings"; import { Settings } from "./Settings/Settings";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { generate } from "escodegen"; import { generate } from "escodegen";
@ -265,7 +264,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
} }
if (interpreter.step()) { if (interpreter.step()) {
setTimeoutRef(runInterpreter, Settings.CodeInstructionRunTime); setTimeout(runInterpreter, Settings.CodeInstructionRunTime);
} else { } else {
resolve(workerScript); resolve(workerScript);
} }

@ -14,7 +14,6 @@ import { staneksGift, loadStaneksGift } from "./CotMG/Helper";
import { GameSavedEvents } from "./ui/React/Snackbar"; import { GameSavedEvents } from "./ui/React/Snackbar";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import * as ExportBonus from "./ExportBonus"; import * as ExportBonus from "./ExportBonus";
import { dialogBoxCreate } from "./ui/React/DialogBox"; import { dialogBoxCreate } from "./ui/React/DialogBox";
@ -105,7 +104,7 @@ class BitburnerSaveObject {
a.download = filename; a.download = filename;
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
setTimeoutRef(function () { setTimeout(function () {
document.body.removeChild(a); document.body.removeChild(a);
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
}, 0); }, 0);

@ -7,7 +7,6 @@
import { calculateRamUsage } from "./RamCalculations"; import { calculateRamUsage } from "./RamCalculations";
import { ScriptUrl } from "./ScriptUrl"; import { ScriptUrl } from "./ScriptUrl";
import { setTimeoutRef } from "../utils/SetTimeoutRef";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { roundToTwo } from "../utils/helpers/roundToTwo"; import { roundToTwo } from "../utils/helpers/roundToTwo";
@ -70,7 +69,7 @@ export class Script {
a.download = filename; a.download = filename;
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
setTimeoutRef(function () { setTimeout(function () {
document.body.removeChild(a); document.body.removeChild(a);
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
}, 0); }, 0);

@ -1,4 +1,5 @@
export interface Options { export interface Options {
theme: string; theme: string;
insertSpaces: boolean; insertSpaces: boolean;
fontSize: number;
} }

@ -8,6 +8,7 @@ import Typography from "@mui/material/Typography";
import Select from "@mui/material/Select"; import Select from "@mui/material/Select";
import Switch from "@mui/material/Switch"; import Switch from "@mui/material/Switch";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import TextField from "@mui/material/TextField";
interface IProps { interface IProps {
options: Options; options: Options;
@ -19,15 +20,23 @@ interface IProps {
export function OptionsModal(props: IProps): React.ReactElement { export function OptionsModal(props: IProps): React.ReactElement {
const [theme, setTheme] = useState(props.options.theme); const [theme, setTheme] = useState(props.options.theme);
const [insertSpaces, setInsertSpaces] = useState(props.options.insertSpaces); const [insertSpaces, setInsertSpaces] = useState(props.options.insertSpaces);
const [fontSize, setFontSize] = useState(props.options.fontSize);
function save(): void { function save(): void {
props.save({ props.save({
theme: theme, theme: theme,
insertSpaces: insertSpaces, insertSpaces: insertSpaces,
fontSize: fontSize,
}); });
props.onClose(); props.onClose();
} }
function onFontChange(event: React.ChangeEvent<HTMLInputElement>): void {
const f = parseFloat(event.target.value);
if (isNaN(f)) return;
setFontSize(f);
}
return ( return (
<Modal open={props.open} onClose={props.onClose}> <Modal open={props.open} onClose={props.onClose}>
<Box display="flex" flexDirection="row" alignItems="center"> <Box display="flex" flexDirection="row" alignItems="center">
@ -42,6 +51,9 @@ export function OptionsModal(props: IProps): React.ReactElement {
<Typography>Use whitespace over tabs: </Typography> <Typography>Use whitespace over tabs: </Typography>
<Switch onChange={(event) => setInsertSpaces(event.target.checked)} checked={insertSpaces} /> <Switch onChange={(event) => setInsertSpaces(event.target.checked)} checked={insertSpaces} />
</Box> </Box>
<Box display="flex" flexDirection="row" alignItems="center">
<TextField type="number" label="Font size" value={fontSize} onChange={onFontChange} />
</Box>
<br /> <br />
<Button onClick={save}>Save</Button> <Button onClick={save}>Save</Button>
</Modal> </Modal>

@ -31,7 +31,7 @@ import IconButton from "@mui/material/IconButton";
import SettingsIcon from "@mui/icons-material/Settings"; import SettingsIcon from "@mui/icons-material/Settings";
let symbols: string[] = []; let symbols: string[] = [];
(function () { export function SetupTextEditor(): void {
const ns = NetscriptFunctions({} as WorkerScript); const ns = NetscriptFunctions({} as WorkerScript);
function populate(ns: any): string[] { function populate(ns: any): string[] {
@ -52,7 +52,7 @@ let symbols: string[] = [];
const exclude = ["heart", "break", "exploit", "bypass", "corporation"]; const exclude = ["heart", "break", "exploit", "bypass", "corporation"];
symbols = symbols.filter((symbol: string) => !exclude.includes(symbol)); symbols = symbols.filter((symbol: string) => !exclude.includes(symbol));
})(); }
interface IProps { interface IProps {
filename: string; filename: string;
@ -87,6 +87,7 @@ export function Root(props: IProps): React.ReactElement {
const [options, setOptions] = useState<Options>({ const [options, setOptions] = useState<Options>({
theme: Settings.MonacoTheme, theme: Settings.MonacoTheme,
insertSpaces: Settings.MonacoInsertSpaces, insertSpaces: Settings.MonacoInsertSpaces,
fontSize: Settings.MonacoFontSize,
}); });
// store the last known state in case we need to restart without nano. // store the last known state in case we need to restart without nano.
@ -329,11 +330,13 @@ export function Root(props: IProps): React.ReactElement {
options={{ options={{
theme: Settings.MonacoTheme, theme: Settings.MonacoTheme,
insertSpaces: Settings.MonacoInsertSpaces, insertSpaces: Settings.MonacoInsertSpaces,
fontSize: Settings.MonacoFontSize,
}} }}
save={(options: Options) => { save={(options: Options) => {
setOptions(options); setOptions(options);
Settings.MonacoTheme = options.theme; Settings.MonacoTheme = options.theme;
Settings.MonacoInsertSpaces = options.insertSpaces; Settings.MonacoInsertSpaces = options.insertSpaces;
Settings.MonacoFontSize = options.fontSize;
}} }}
/> />
</> </>

@ -74,6 +74,7 @@ export function processSingleServerGrowth(server: Server, threads: number, p: IP
} }
const oldMoneyAvailable = server.moneyAvailable; const oldMoneyAvailable = server.moneyAvailable;
server.moneyAvailable += 1 * threads; // It can be grown even if it has no money
server.moneyAvailable *= serverGrowth; server.moneyAvailable *= serverGrowth;
// in case of data corruption // in case of data corruption

@ -150,6 +150,8 @@ interface ISettings extends IDefaultSettings {
MonacoTheme: string; MonacoTheme: string;
MonacoInsertSpaces: boolean; MonacoInsertSpaces: boolean;
MonacoFontSize: number;
} }
export const defaultSettings: IDefaultSettings = { export const defaultSettings: IDefaultSettings = {
@ -232,6 +234,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup, SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup,
MonacoTheme: "vs-dark", MonacoTheme: "vs-dark",
MonacoInsertSpaces: false, MonacoInsertSpaces: false,
MonacoFontSize: 10,
theme: { theme: {
primarylight: defaultSettings.theme.primarylight, primarylight: defaultSettings.theme.primarylight,

@ -13,10 +13,11 @@ export const TerminalHelpText: string[] = [
"check [script] [args...] Print a script's logs to Terminal", "check [script] [args...] Print a script's logs to Terminal",
"clear Clear all text on the terminal ", "clear Clear all text on the terminal ",
"cls See 'clear' command ", "cls See 'clear' command ",
"connect [ip/hostname] Connects to a remote server", "connect [hostname] Connects to a remote server",
"download [script/text file] Downloads scripts or text files to your computer", "download [script/text file] Downloads scripts or text files to your computer",
"expr [math expression] Evaluate a mathematical expression", "expr [math expression] Evaluate a mathematical expression",
"free Check the machine's memory (RAM) usage", "free Check the machine's memory (RAM) usage",
"grow Spoof money in a servers bank account, increasing the amount available.",
"hack Hack the current machine", "hack Hack the current machine",
"help [command] Display this help text, or the help text for a command", "help [command] Display this help text, or the help text for a command",
"home Connect to home computer", "home Connect to home computer",
@ -39,6 +40,7 @@ export const TerminalHelpText: string[] = [
"tail [script] [args...] Displays dynamic logs for the specified script", "tail [script] [args...] Displays dynamic logs for the specified script",
"top Displays all running scripts and their RAM usage", "top Displays all running scripts and their RAM usage",
"unalias [alias name] Deletes the specified alias", "unalias [alias name] Deletes the specified alias",
"weaken [server] Reduce the security of a server",
"wget [url] [target file] Retrieves code/text from a web server", "wget [url] [target file] Retrieves code/text from a web server",
]; ];
@ -151,7 +153,7 @@ export const HelpTexts: IMap<string[]> = {
"and down arrow keys is still valid. Also note that this is permanent and there is no way to undo this. Synonymous with 'clear' command", "and down arrow keys is still valid. Also note that this is permanent and there is no way to undo this. Synonymous with 'clear' command",
], ],
connect: [ connect: [
"connect [hostname/ip]", "connect [hostname]",
" ", " ",
"Connect to a remote server. The hostname or IP address of the remote server must be given as the argument ", "Connect to a remote server. The hostname or IP address of the remote server must be given as the argument ",
"to this command. Note that only servers that are immediately adjacent to the current server in the network can be connected to. To ", "to this command. Note that only servers that are immediately adjacent to the current server in the network can be connected to. To ",
@ -190,6 +192,12 @@ export const HelpTexts: IMap<string[]> = {
"Display's the memory usage on the current machine. Print the amount of RAM that is available on the current server as well as ", "Display's the memory usage on the current machine. Print the amount of RAM that is available on the current server as well as ",
"how much of it is being used.", "how much of it is being used.",
], ],
grow: [
"grow",
"",
"Spoof transactions in the current server. Increasing the money available by hacking. Requires root access.",
"See the wiki page for hacking mechanics.",
],
hack: [ hack: [
"hack", "hack",
" ", " ",
@ -394,6 +402,12 @@ export const HelpTexts: IMap<string[]> = {
" ", " ",
"It is not necessary to differentiate between global and non-global aliases when using 'unalias'", "It is not necessary to differentiate between global and non-global aliases when using 'unalias'",
], ],
weaken: [
"weaken",
"",
"Reduces the security level of the current server. Decreasing the time it takes for all operations on this server.",
"Requires root access. See the wiki page for hacking mechanics.",
],
wget: [ wget: [
"wget [url] [target file]", "wget [url] [target file]",
" ", " ",

@ -31,9 +31,9 @@ export class Link {
export class TTimer { export class TTimer {
time: number; time: number;
timeLeft: number; timeLeft: number;
action: "h" | "b" | "a"; action: "h" | "b" | "a" | "g" | "w";
constructor(time: number, action: "h" | "b" | "a") { constructor(time: number, action: "h" | "b" | "a" | "g" | "w") {
this.time = time; this.time = time;
this.timeLeft = time; this.timeLeft = time;
this.action = action; this.action = action;
@ -62,7 +62,11 @@ export interface ITerminal {
startAnalyze(): void; startAnalyze(): void;
startBackdoor(player: IPlayer): void; startBackdoor(player: IPlayer): void;
startHack(player: IPlayer): void; startHack(player: IPlayer): void;
startGrow(player: IPlayer): void;
startWeaken(player: IPlayer): void;
finishHack(router: IRouter, player: IPlayer, cancelled?: boolean): void; finishHack(router: IRouter, player: IPlayer, cancelled?: boolean): void;
finishGrow(player: IPlayer, cancelled?: boolean): void;
finishWeaken(player: IPlayer, cancelled?: boolean): void;
finishBackdoor(router: IRouter, player: IPlayer, cancelled?: boolean): void; finishBackdoor(router: IRouter, player: IPlayer, cancelled?: boolean): void;
finishAnalyze(player: IPlayer, cancelled?: boolean): void; finishAnalyze(player: IPlayer, cancelled?: boolean): void;
finishAction(router: IRouter, player: IPlayer, cancelled?: boolean): void; finishAction(router: IRouter, player: IPlayer, cancelled?: boolean): void;

@ -17,7 +17,7 @@ import { AllServers } from "../Server/AllServers";
import { removeLeadingSlash, isInRootDirectory, evaluateFilePath } from "./DirectoryHelpers"; import { removeLeadingSlash, isInRootDirectory, evaluateFilePath } from "./DirectoryHelpers";
import { checkIfConnectedToDarkweb } from "../DarkWeb/DarkWeb"; import { checkIfConnectedToDarkweb } from "../DarkWeb/DarkWeb";
import { iTutorialNextStep, iTutorialSteps, ITutorial } from "../InteractiveTutorial"; import { iTutorialNextStep, iTutorialSteps, ITutorial } from "../InteractiveTutorial";
import { GetServerByHostname, getServer, getServerOnNetwork } from "../Server/ServerHelpers"; import { GetServerByHostname, getServer, getServerOnNetwork, processSingleServerGrowth } from "../Server/ServerHelpers";
import { ParseCommand, ParseCommands } from "./Parser"; import { ParseCommand, ParseCommands } from "./Parser";
import { SpecialServerIps, SpecialServerNames } from "../Server/SpecialServerIps"; import { SpecialServerIps, SpecialServerNames } from "../Server/SpecialServerIps";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
@ -27,6 +27,8 @@ import {
calculateHackingExpGain, calculateHackingExpGain,
calculatePercentMoneyHacked, calculatePercentMoneyHacked,
calculateHackingTime, calculateHackingTime,
calculateGrowTime,
calculateWeakenTime,
} from "../Hacking"; } from "../Hacking";
import { numeralWrapper } from "../ui/numeralFormat"; import { numeralWrapper } from "../ui/numeralFormat";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
@ -42,6 +44,7 @@ import { connect } from "./commands/connect";
import { download } from "./commands/download"; import { download } from "./commands/download";
import { expr } from "./commands/expr"; import { expr } from "./commands/expr";
import { free } from "./commands/free"; import { free } from "./commands/free";
import { grow } from "./commands/grow";
import { hack } from "./commands/hack"; import { hack } from "./commands/hack";
import { help } from "./commands/help"; import { help } from "./commands/help";
import { home } from "./commands/home"; import { home } from "./commands/home";
@ -64,6 +67,7 @@ import { sudov } from "./commands/sudov";
import { tail } from "./commands/tail"; import { tail } from "./commands/tail";
import { top } from "./commands/top"; import { top } from "./commands/top";
import { unalias } from "./commands/unalias"; import { unalias } from "./commands/unalias";
import { weaken } from "./commands/weaken";
import { wget } from "./commands/wget"; import { wget } from "./commands/wget";
export class Terminal implements ITerminal { export class Terminal implements ITerminal {
@ -115,6 +119,23 @@ export class Terminal implements ITerminal {
this.startAction(calculateHackingTime(server, player) / 4, "h"); this.startAction(calculateHackingTime(server, player) / 4, "h");
} }
startGrow(player: IPlayer): void {
const server = player.getCurrentServer();
if (server instanceof HacknetServer) {
this.error("Cannot hack this kind of server");
return;
}
this.startAction(calculateGrowTime(server, player) / 16, "g");
}
startWeaken(player: IPlayer): void {
const server = player.getCurrentServer();
if (server instanceof HacknetServer) {
this.error("Cannot hack this kind of server");
return;
}
this.startAction(calculateWeakenTime(server, player) / 16, "w");
}
startBackdoor(player: IPlayer): void { startBackdoor(player: IPlayer): void {
// Backdoor should take the same amount of time as hack // Backdoor should take the same amount of time as hack
const server = player.getCurrentServer(); const server = player.getCurrentServer();
@ -130,7 +151,7 @@ export class Terminal implements ITerminal {
this.startAction(1, "a"); this.startAction(1, "a");
} }
startAction(n: number, action: "h" | "b" | "a"): void { startAction(n: number, action: "h" | "b" | "a" | "g" | "w"): void {
this.action = new TTimer(n, action); this.action = new TTimer(n, action);
} }
@ -183,7 +204,6 @@ export class Terminal implements ITerminal {
); );
} else { } else {
// Failure // Failure
// player only gains 25% exp for failure? TODO Can change this later to balance
player.gainHackingExp(expGainedOnFailure); player.gainHackingExp(expGainedOnFailure);
this.print( this.print(
`Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`, `Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`,
@ -191,6 +211,39 @@ export class Terminal implements ITerminal {
} }
} }
finishGrow(player: IPlayer, cancelled = false): void {
if (cancelled) return;
const server = player.getCurrentServer();
if (server instanceof HacknetServer) {
this.error("Cannot hack this kind of server");
return;
}
const expGain = calculateHackingExpGain(server, player);
const growth = processSingleServerGrowth(server, 1, player, server.cpuCores) - 1;
this.print(
`Available money on '${server.hostname}' grown by ${numeralWrapper.formatPercentage(
growth,
6,
)}. Gained ${numeralWrapper.formatExp(expGain)} hacking exp.`,
);
}
finishWeaken(player: IPlayer, cancelled = false): void {
if (cancelled) return;
const server = player.getCurrentServer();
if (server instanceof HacknetServer) {
this.error("Cannot hack this kind of server");
return;
}
const expGain = calculateHackingExpGain(server, player);
server.weaken(CONSTANTS.ServerWeakenAmount);
this.print(
`'${server.hostname}' security level weakened to ${server.hackDifficulty}. Gained ${numeralWrapper.formatExp(
expGain,
)} hacking exp.`,
);
}
finishBackdoor(router: IRouter, player: IPlayer, cancelled = false): void { finishBackdoor(router: IRouter, player: IPlayer, cancelled = false): void {
if (!cancelled) { if (!cancelled) {
const server = player.getCurrentServer(); const server = player.getCurrentServer();
@ -257,6 +310,10 @@ export class Terminal implements ITerminal {
this.print(this.getProgressText()); this.print(this.getProgressText());
if (this.action.action === "h") { if (this.action.action === "h") {
this.finishHack(router, player, cancelled); this.finishHack(router, player, cancelled);
} else if (this.action.action === "g") {
this.finishGrow(player, cancelled);
} else if (this.action.action === "w") {
this.finishWeaken(player, cancelled);
} else if (this.action.action === "b") { } else if (this.action.action === "b") {
this.finishBackdoor(router, player, cancelled); this.finishBackdoor(router, player, cancelled);
} else if (this.action.action === "a") { } else if (this.action.action === "a") {
@ -657,6 +714,7 @@ export class Terminal implements ITerminal {
args: (string | number)[], args: (string | number)[],
) => void; ) => void;
} = { } = {
"scan-analyze": scananalyze,
alias: alias, alias: alias,
analyze: analyze, analyze: analyze,
backdoor: backdoor, backdoor: backdoor,
@ -664,12 +722,13 @@ export class Terminal implements ITerminal {
cat: cat, cat: cat,
cd: cd, cd: cd,
check: check, check: check,
cls: () => this.clear(),
clear: () => this.clear(), clear: () => this.clear(),
cls: () => this.clear(),
connect: connect, connect: connect,
download: download, download: download,
expr: expr, expr: expr,
free: free, free: free,
grow: grow,
hack: hack, hack: hack,
help: help, help: help,
home: home, home: home,
@ -686,12 +745,12 @@ export class Terminal implements ITerminal {
rm: rm, rm: rm,
run: run, run: run,
scan: scan, scan: scan,
"scan-analyze": scananalyze,
scp: scp, scp: scp,
sudov: sudov, sudov: sudov,
tail: tail, tail: tail,
top: top, top: top,
unalias: unalias, unalias: unalias,
weaken: weaken,
wget: wget, wget: wget,
}; };

@ -0,0 +1,44 @@
import { ITerminal } from "../ITerminal";
import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { Server } from "../../Server/Server";
export function grow(
terminal: ITerminal,
router: IRouter,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 0) {
terminal.error("Incorrect usage of grow command. Usage: grow");
return;
}
if (!(server instanceof Server)) {
terminal.error(
"Cannot grow your own machines! You are currently connected to your home PC or one of your purchased servers",
);
}
const normalServer = server as Server;
// Hack the current PC (usually for money)
// You can't grow your home pc or servers you purchased
if (normalServer.purchasedByPlayer) {
terminal.error(
"Cannot grow your own machines! You are currently connected to your home PC or one of your purchased servers",
);
return;
}
if (!normalServer.hasAdminRights) {
terminal.error("You do not have admin rights for this machine! Cannot grow");
return;
}
if (normalServer.requiredHackingSkill > player.hacking_skill) {
terminal.error(
"Your hacking skill is not high enough to attempt hacking this machine. Try analyzing the machine to determine the required hacking skill",
);
return;
}
terminal.startGrow(player);
}

@ -0,0 +1,44 @@
import { ITerminal } from "../ITerminal";
import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { Server } from "../../Server/Server";
export function weaken(
terminal: ITerminal,
router: IRouter,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 0) {
terminal.error("Incorrect usage of weaken command. Usage: weaken");
return;
}
if (!(server instanceof Server)) {
terminal.error(
"Cannot weaken your own machines! You are currently connected to your home PC or one of your purchased servers",
);
}
const normalServer = server as Server;
// Hack the current PC (usually for money)
// You can't weaken your home pc or servers you purchased
if (normalServer.purchasedByPlayer) {
terminal.error(
"Cannot weaken your own machines! You are currently connected to your home PC or one of your purchased servers",
);
return;
}
if (!normalServer.hasAdminRights) {
terminal.error("You do not have admin rights for this machine! Cannot weaken");
return;
}
if (normalServer.requiredHackingSkill > player.hacking_skill) {
terminal.error(
"Your hacking skill is not high enough to attempt hacking this machine. Try analyzing the machine to determine the required hacking skill",
);
return;
}
terminal.startWeaken(player);
}

@ -21,6 +21,7 @@ const commands = [
"download", "download",
"expr", "expr",
"free", "free",
"grow",
"hack", "hack",
"help", "help",
"home", "home",
@ -36,13 +37,14 @@ const commands = [
"ps", "ps",
"rm", "rm",
"run", "run",
"scan",
"scan-analyze", "scan-analyze",
"scan",
"scp", "scp",
"sudov", "sudov",
"tail", "tail",
"theme", "theme",
"top", "top",
"weaken",
]; ];
export function determineAllPossibilitiesForTabCompletion( export function determineAllPossibilitiesForTabCompletion(

@ -4,7 +4,7 @@ import { Theme } from "@mui/material/styles";
import makeStyles from "@mui/styles/makeStyles"; import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles"; import createStyles from "@mui/styles/createStyles";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Paper from "@mui/material/Paper"; import Tooltip from "@mui/material/Tooltip";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../utils/helpers/keyCodes";
import { ITerminal } from "../ITerminal"; import { ITerminal } from "../ITerminal";
@ -18,7 +18,6 @@ const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
textfield: { textfield: {
margin: theme.spacing(0), margin: theme.spacing(0),
width: "100%",
}, },
input: { input: {
backgroundColor: "#000", backgroundColor: "#000",
@ -330,23 +329,30 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
return ( return (
<> <>
{possibilities.length > 0 && ( <Tooltip
<Paper square> title={
possibilities.length > 0 ? (
<>
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}> <Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
Possible autocomplete candidate: Possible autocomplete candidate:
</Typography> </Typography>
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}> <Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
{possibilities.join(" ")} {possibilities.join(" ")}
</Typography> </Typography>
</Paper> </>
)} ) : (
""
)
}
>
<TextField <TextField
fullWidth
color={terminal.action === null ? "primary" : "secondary"} color={terminal.action === null ? "primary" : "secondary"}
autoFocus autoFocus
disabled={terminal.action !== null} disabled={terminal.action !== null}
autoComplete="off" autoComplete="off"
classes={{ root: classes.textfield }}
value={value} value={value}
classes={{ root: classes.textfield }}
onChange={handleValueChange} onChange={handleValueChange}
inputRef={terminalInput} inputRef={terminalInput}
InputProps={{ InputProps={{
@ -354,16 +360,15 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
id: "terminal-input", id: "terminal-input",
className: classes.input, className: classes.input,
startAdornment: ( startAdornment: (
<>
<Typography color={terminal.action === null ? "primary" : "secondary"} flexShrink={0}> <Typography color={terminal.action === null ? "primary" : "secondary"} flexShrink={0}>
[{player.getCurrentServer().hostname}&nbsp;~{terminal.cwd()}]&gt;&nbsp; [{player.getCurrentServer().hostname}&nbsp;~{terminal.cwd()}]&gt;&nbsp;
</Typography> </Typography>
</>
), ),
spellCheck: false, spellCheck: false,
onKeyDown: onKeyDown, onKeyDown: onKeyDown,
}} }}
></TextField> ></TextField>
</Tooltip>
</> </>
); );
} }

@ -1,4 +1,3 @@
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { dialogBoxCreate } from "./ui/React/DialogBox"; import { dialogBoxCreate } from "./ui/React/DialogBox";
import { BaseServer } from "./Server/BaseServer"; import { BaseServer } from "./Server/BaseServer";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "./utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "./utils/JSONReviver";
@ -47,7 +46,7 @@ export class TextFile {
a.download = this.fn; a.download = this.fn;
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
setTimeoutRef(() => { setTimeout(() => {
document.body.removeChild(a); document.body.removeChild(a);
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
}, 0); }, 0);

@ -15,6 +15,7 @@ import { Factions, initFactions } from "./Faction/Factions";
import { staneksGift } from "./CotMG/Helper"; import { staneksGift } from "./CotMG/Helper";
import { processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers"; import { processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers";
import { Router } from "./ui/GameRoot"; import { Router } from "./ui/GameRoot";
import { SetupTextEditor } from "./ScriptEditor/ui/Root";
import { import {
getHackingWorkRepGain, getHackingWorkRepGain,
@ -45,7 +46,7 @@ import { Reputation } from "./ui/React/Reputation";
import { AlertEvents } from "./ui/React/AlertManager"; import { AlertEvents } from "./ui/React/AlertManager";
import { exceptionAlert } from "./utils/helpers/exceptionAlert"; import { exceptionAlert } from "./utils/helpers/exceptionAlert";
import { startTampering } from "./Exploits/tampering"; import { startExploits } from "./Exploits/loops";
import React from "react"; import React from "react";
@ -266,7 +267,7 @@ const Engine: {
}, },
load: function (saveString) { load: function (saveString) {
startTampering(); startExploits();
// Load game from save or create new game // Load game from save or create new game
if (loadGame(saveString)) { if (loadGame(saveString)) {
ThemeEvents.emit(); ThemeEvents.emit();
@ -426,6 +427,7 @@ const Engine: {
// Start interactive tutorial // Start interactive tutorial
iTutorialStart(); iTutorialStart();
} }
SetupTextEditor();
}, },
start: function () { start: function () {

@ -285,7 +285,9 @@ export function InteractiveTutorialRoot(): React.ReactElement {
<br /> <br />
<br /> <br />
The amount of money on a server is not limitless. So, if you constantly hack a server and deplete its money, The amount of money on a server is not limitless. So, if you constantly hack a server and deplete its money,
then you will encounter diminishing returns in your hacking. then you will encounter diminishing returns in your hacking. You will need to use{" "}
<Typography classes={{ root: classes.textfield }}>{"[n00dles ~/]> grow"}</Typography>
and <Typography classes={{ root: classes.textfield }}>{"[n00dles ~/]> weaken"}</Typography>
</Typography> </Typography>
), ),
canNext: true, canNext: true,

@ -42,7 +42,7 @@ export function AlertManager(): React.ReactElement {
<> <>
{alerts.length > 0 && ( {alerts.length > 0 && (
<Modal open={true} onClose={close}> <Modal open={true} onClose={close}>
<Box> <Box overflow="scroll" sx={{ overflowWrap: "break-word", whiteSpace: "pre-line" }}>
<Typography>{alerts[0].text}</Typography> <Typography>{alerts[0].text}</Typography>
</Box> </Box>
</Modal> </Modal>

@ -147,7 +147,7 @@ function LogWindow(props: IProps): React.ReactElement {
</Box> </Box>
</Paper> </Paper>
<Paper> <Paper>
<Box maxHeight="25vh" overflow="scroll" sx={{ overflowWrap: "break-word" }}> <Box maxHeight="25vh" overflow="scroll" sx={{ overflowWrap: "break-word", whiteSpace: "pre-line" }}>
{props.script.logs.map( {props.script.logs.map(
(line: string, i: number): JSX.Element => ( (line: string, i: number): JSX.Element => (
<Typography key={i}> <Typography key={i}>

@ -1,5 +0,0 @@
// This is a reference to the native setTimeout() function
// setTimeout() is used in various places around the game's source code.
// This reference is used to make sure that if players alter window.setTimeout()
// through NetscriptJS, then the game will still function properly
export const setTimeoutRef = window.setTimeout.bind(window);