From bb0bdb776bc94c6386d0ffde056b7b91a20ec2d6 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 4 Oct 2021 22:25:21 -0400 Subject: [PATCH] extracted some of the Netscript functions into their own file. --- src/NetscriptFunctions.ts | 454 ++------------------- src/NetscriptFunctions/Extra.ts | 39 ++ src/NetscriptFunctions/Gang.ts | 4 +- src/NetscriptFunctions/Hacknet.ts | 213 ++++++++++ src/NetscriptFunctions/INetscriptHelper.ts | 3 + src/NetscriptFunctions/Sleeve.ts | 333 +++++++++++++++ 6 files changed, 615 insertions(+), 431 deletions(-) create mode 100644 src/NetscriptFunctions/Extra.ts create mode 100644 src/NetscriptFunctions/Hacknet.ts create mode 100644 src/NetscriptFunctions/Sleeve.ts diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index cd01093ae..ef5145cdc 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -57,7 +57,6 @@ import { calculateServerGrowth } from "./Server/formulas/grow"; import { AllGangs } from "./Gang/AllGangs"; import { Factions, factionExists } from "./Faction/Factions"; import { joinFaction, purchaseAugmentation } from "./Faction/FactionHelpers"; -import { FactionWorkType } from "./Faction/FactionWorkTypeEnum"; import { netscriptCanGrow, netscriptCanHack, netscriptCanWeaken } from "./Hacking/netscriptCanHack"; import { @@ -133,9 +132,6 @@ import { workerScripts } from "./Netscript/WorkerScripts"; import { WorkerScript } from "./Netscript/WorkerScript"; import { makeRuntimeRejectMsg, netscriptDelay, resolveNetscriptRequestedThreads } from "./NetscriptEvaluator"; 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 { numeralWrapper } from "./ui/numeralFormat"; @@ -161,6 +157,9 @@ import { CodingContract } from "./CodingContracts"; import { Stock } from "./StockMarket/Stock"; import { BaseServer } from "./Server/BaseServer"; import { INetscriptGang, NetscriptGang } from "./NetscriptFunctions/Gang"; +import { INetscriptSleeve, NetscriptSleeve } from "./NetscriptFunctions/Sleeve"; +import { INetscriptExtra, NetscriptExtra } from "./NetscriptFunctions/Extra"; +import { INetscriptHacknet, NetscriptHacknet } from "./NetscriptFunctions/Hacknet"; const defaultInterpreter = new Interpreter("", () => undefined); @@ -197,9 +196,11 @@ function toNative(pseudoObj: any): any { return nativeObj; } -interface NS { +interface NS extends INetscriptExtra { [key: string]: any; + hacknet: INetscriptHacknet; gang: INetscriptGang; + sleeve: INetscriptSleeve; } function NetscriptFunctions(workerScript: WorkerScript): NS { @@ -366,35 +367,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 errstack = new Error().stack; if (errstack === undefined) throw new Error("how did we not throw an error?"); @@ -510,23 +482,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 server = safeGetServer(ip, func); const contract = server.getContract(fn); @@ -758,146 +713,28 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { const helper = { updateDynamicRam: updateDynamicRam, makeRuntimeErrorMsg: makeRuntimeErrorMsg, + 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`); + }, + number: (funcName: string, argName: string, v: any): number => { + if (typeof v === "number") return v; + if (!isNaN(v) && !isNaN(parseFloat(v))) return parseFloat(v); + throw makeRuntimeErrorMsg(funcName, `${argName} should be a number`); + }, + boolean: (v: any): boolean => { + return !!v; // Just convert it to boolean. + }, }; const gang = NetscriptGang(Player, workerScript, helper); + const sleeve = NetscriptSleeve(Player, workerScript, helper); + const extra = NetscriptExtra(Player, workerScript, helper); + const hacknet = NetscriptHacknet(Player, workerScript, helper); const functions = { - hacknet: { - 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 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(); - }, - }, + hacknet: hacknet, sprintf: sprintf, vsprintf: vsprintf, scan: function (ip: any = workerScript.serverIp, hostnames: any = true): any { @@ -4653,227 +4490,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { }, }, // End coding contracts - // Duplicate Sleeve API - 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 - 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 + sleeve: sleeve, formulas: { basic: { calculateSkill: function (exp: any, mult: any = 1): any { @@ -4981,28 +4598,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { }, }, }, // 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 { data = toNative(data); // We always want the help flag. @@ -5035,6 +4630,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { } return ret; }, + ...extra, }; function getFunctionNames(obj: NS): string[] { diff --git a/src/NetscriptFunctions/Extra.ts b/src/NetscriptFunctions/Extra.ts new file mode 100644 index 000000000..74fc21010 --- /dev/null +++ b/src/NetscriptFunctions/Extra.ts @@ -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; + }, + }; +} diff --git a/src/NetscriptFunctions/Gang.ts b/src/NetscriptFunctions/Gang.ts index 0896caf3f..0c487308f 100644 --- a/src/NetscriptFunctions/Gang.ts +++ b/src/NetscriptFunctions/Gang.ts @@ -10,7 +10,7 @@ import { GangMember } from "../Gang/GangMember"; import { GangMemberTask } from "../Gang/GangMemberTask"; export interface INetscriptGang { - createGang(faction: any): boolean; + createGang(faction: string): boolean; inGang(): boolean; getMemberNames(): string[]; getGangInformation(): any; @@ -59,7 +59,7 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe }; return { - createGang: function (faction: string): any { + createGang: function (faction: string): boolean { helper.updateDynamicRam("createGang", getRamCost("gang", "createGang")); // this list is copied from Faction/ui/Root.tsx const GangNames = [ diff --git a/src/NetscriptFunctions/Hacknet.ts b/src/NetscriptFunctions/Hacknet.ts new file mode 100644 index 000000000..9c24e0ab4 --- /dev/null +++ b/src/NetscriptFunctions/Hacknet.ts @@ -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(); + }, + }; +} diff --git a/src/NetscriptFunctions/INetscriptHelper.ts b/src/NetscriptFunctions/INetscriptHelper.ts index 26c5ed286..58bbab059 100644 --- a/src/NetscriptFunctions/INetscriptHelper.ts +++ b/src/NetscriptFunctions/INetscriptHelper.ts @@ -1,4 +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; } diff --git a/src/NetscriptFunctions/Sleeve.ts b/src/NetscriptFunctions/Sleeve.ts new file mode 100644 index 000000000..3bcfdb631 --- /dev/null +++ b/src/NetscriptFunctions/Sleeve.ts @@ -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); + }, + }; +}