diff --git a/src/Augmentation/AugmentationCreator.tsx b/src/Augmentation/AugmentationCreator.tsx index cf145ce4c..5c8d17055 100644 --- a/src/Augmentation/AugmentationCreator.tsx +++ b/src/Augmentation/AugmentationCreator.tsx @@ -5,7 +5,7 @@ import { Programs } from "../Programs/Programs"; import { WHRNG } from "../Casino/RNG"; import React from "react"; import { FactionNames } from "../Faction/data/FactionNames"; -import { CityName } from "src/Locations/data/CityNames"; +import { CityName } from "../Locations/data/CityNames"; function getRandomBonus(): any { const bonuses = [ diff --git a/src/Infiltration/formulas/game.ts b/src/Infiltration/formulas/game.ts new file mode 100644 index 000000000..2b98bc239 --- /dev/null +++ b/src/Infiltration/formulas/game.ts @@ -0,0 +1,25 @@ +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { calculateSkill } from "../../PersonObjects/formulas/skill"; + +function calculateRawDiff(player: IPlayer, stats: number, startingDifficulty: number): number { + const difficulty = startingDifficulty - Math.pow(stats, 0.9) / 250 - player.intelligence / 1600; + if (difficulty < 0) return 0; + if (difficulty > 3) return 3; + return difficulty; +} + +export function calculateDifficulty(player: IPlayer, startingSecurityLevel: number): number { + const totalStats = player.strength + player.defense + player.dexterity + player.agility + player.charisma; + return calculateRawDiff(player, totalStats, startingSecurityLevel); +} + +export function calculateReward(player: IPlayer, startingSecurityLevel: number): number { + const xpMult = 10 * 60 * 15; + const total = + calculateSkill(player.strength_exp_mult * xpMult, player.strength_mult) + + calculateSkill(player.defense_exp_mult * xpMult, player.defense_mult) + + calculateSkill(player.agility_exp_mult * xpMult, player.agility_mult) + + calculateSkill(player.dexterity_exp_mult * xpMult, player.dexterity_mult) + + calculateSkill(player.charisma_exp_mult * xpMult, player.charisma_mult); + return calculateRawDiff(player, total, startingSecurityLevel); +} diff --git a/src/Infiltration/formulas/victory.ts b/src/Infiltration/formulas/victory.ts new file mode 100644 index 000000000..21d646cfa --- /dev/null +++ b/src/Infiltration/formulas/victory.ts @@ -0,0 +1,49 @@ +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; +import { LocationsMetadata } from "../../Locations/data/LocationsMetadata"; + +export function calculateSellInformationCashReward( + player: IPlayer, + reward: number, + maxLevel: number, + difficulty: number, +): number { + const levelBonus = maxLevel * Math.pow(1.01, maxLevel); + + return ( + Math.pow(reward + 1, 2) * + Math.pow(difficulty, 3) * + 3e3 * + levelBonus * + player.infiltration_sell_mult * + BitNodeMultipliers.InfiltrationMoney + ); +} + +export function calculateTradeInformationRepReward( + player: IPlayer, + reward: number, + maxLevel: number, + difficulty: number, +): number { + const levelBonus = maxLevel * Math.pow(1.01, maxLevel); + + return ( + Math.pow(reward + 1, 2) * + Math.pow(difficulty, 3) * + 3e3 * + levelBonus * + player.infiltration_sell_mult * + BitNodeMultipliers.InfiltrationMoney + ); +} + +export function calculateInfiltratorsRepReward(player: IPlayer, difficulty: number): number { + const maxStartingSecurityLevel = LocationsMetadata.reduce((acc, data): number => { + const startingSecurityLevel = data.infiltrationData?.startingSecurityLevel || 0; + return acc > startingSecurityLevel ? acc : startingSecurityLevel; + }, 0); + const baseRepGain = (difficulty / maxStartingSecurityLevel) * 10; + + return (baseRepGain + player.infiltration_base_rep_increase) * player.infiltration_rep_mult; +} diff --git a/src/Infiltration/ui/InfiltrationRoot.tsx b/src/Infiltration/ui/InfiltrationRoot.tsx index ec1b3c0b7..1c78b5b19 100644 --- a/src/Infiltration/ui/InfiltrationRoot.tsx +++ b/src/Infiltration/ui/InfiltrationRoot.tsx @@ -1,47 +1,22 @@ -import { IPlayer } from "../../PersonObjects/IPlayer"; import React, { useState } from "react"; import { Intro } from "./Intro"; import { Game } from "./Game"; import { Location } from "../../Locations/Location"; import { use } from "../../ui/Context"; -import { calculateSkill } from "../../PersonObjects/formulas/skill"; - +import { calculateDifficulty, calculateReward } from "../formulas/game"; interface IProps { location: Location; } -function calcRawDiff(player: IPlayer, stats: number, startingDifficulty: number): number { - const difficulty = startingDifficulty - Math.pow(stats, 0.9) / 250 - player.intelligence / 1600; - if (difficulty < 0) return 0; - if (difficulty > 3) return 3; - return difficulty; -} - -function calcDifficulty(player: IPlayer, startingDifficulty: number): number { - const totalStats = player.strength + player.defense + player.dexterity + player.agility + player.charisma; - return calcRawDiff(player, totalStats, startingDifficulty); -} - -function calcReward(player: IPlayer, startingDifficulty: number): number { - const xpMult = 10 * 60 * 15; - const total = - calculateSkill(player.strength_exp_mult * xpMult, player.strength_mult) + - calculateSkill(player.defense_exp_mult * xpMult, player.defense_mult) + - calculateSkill(player.agility_exp_mult * xpMult, player.agility_mult) + - calculateSkill(player.dexterity_exp_mult * xpMult, player.dexterity_mult) + - calculateSkill(player.charisma_exp_mult * xpMult, player.charisma_mult); - return calcRawDiff(player, total, startingDifficulty); -} - export function InfiltrationRoot(props: IProps): React.ReactElement { const player = use.Player(); const router = use.Router(); const [start, setStart] = useState(false); if (props.location.infiltrationData === undefined) throw new Error("Trying to do infiltration on invalid location."); - const startingDifficulty = props.location.infiltrationData.startingSecurityLevel; - const difficulty = calcDifficulty(player, startingDifficulty); - const reward = calcReward(player, startingDifficulty); + const startingSecurityLevel = props.location.infiltrationData.startingSecurityLevel; + const difficulty = calculateDifficulty(player, startingSecurityLevel); + const reward = calculateReward(player, startingSecurityLevel); function cancel(): void { router.toCity(); @@ -61,7 +36,7 @@ export function InfiltrationRoot(props: IProps): React.ReactElement { return ( { - const startingSecurityLevel = data.infiltrationData?.startingSecurityLevel || 0; - return acc > startingSecurityLevel ? acc : startingSecurityLevel; - }, 0); - const baseRepGain = (props.StartingDifficulty / maxStartingSecurityLevel) * 10; - - return (baseRepGain + player.infiltration_base_rep_increase) * player.infiltration_rep_mult; - } - function sell(): void { handleInfiltrators(); player.gainMoney(moneyGain, "infiltration"); @@ -81,7 +61,7 @@ export function Victory(props: IProps): React.ReactElement { function handleInfiltrators(): void { player.hasCompletedAnInfiltration = true; if (isMemberOfInfiltrators) { - infiltratorFaction.playerReputation += calculateInfiltratorsRepReward(); + infiltratorFaction.playerReputation += infiltrationRepGain; } } @@ -96,7 +76,7 @@ export function Victory(props: IProps): React.ReactElement { You{" "} {isMemberOfInfiltrators ? ( <> - have gained {formatNumber(calculateInfiltratorsRepReward(), 2)} rep for {FactionNames.Infiltrators} and{" "} + have gained {formatNumber(infiltrationRepGain, 2)} rep for {FactionNames.Infiltrators} and{" "} ) : ( <> diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index af50c8c5c..e707488df 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -69,6 +69,11 @@ export const RamCostConstants: IMap = { ScriptStanekPlace: 5, ScriptStanekFragmentAt: 2, ScriptStanekDeleteAt: 0.15, + + ScriptInfiltrationCalculateDifficulty: 2.5, + ScriptInfiltrationCalculateRewards: 2.5, + ScriptInfiltrationGetLocations: 5, + ScriptInfiltrationGetInfiltrations: 15, }; function SF4Cost(cost: number): (player: IPlayer) => number { @@ -376,6 +381,13 @@ export const RamCosts: IMap = { remove: RamCostConstants.ScriptStanekDeleteAt, }, + infiltration: { + calculateDifficulty: RamCostConstants.ScriptInfiltrationCalculateDifficulty, + calculateRewards: RamCostConstants.ScriptInfiltrationCalculateRewards, + calculateGetLocations: RamCostConstants.ScriptInfiltrationGetLocations, + calculateGetInfiltrations: RamCostConstants.ScriptInfiltrationGetInfiltrations, + }, + ui: { getTheme: 0, setTheme: 0, diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index 92b7d8efb..345287d7d 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -64,6 +64,7 @@ import { NetscriptSleeve } from "./NetscriptFunctions/Sleeve"; import { NetscriptExtra } from "./NetscriptFunctions/Extra"; import { NetscriptHacknet } from "./NetscriptFunctions/Hacknet"; import { NetscriptStanek } from "./NetscriptFunctions/Stanek"; +import { NetscriptInfiltration } from "./NetscriptFunctions/Infiltration"; import { NetscriptUserInterface } from "./NetscriptFunctions/UserInterface"; import { NetscriptBladeburner } from "./NetscriptFunctions/Bladeburner"; import { NetscriptCodingContract } from "./NetscriptFunctions/CodingContract"; @@ -78,6 +79,7 @@ import { Gang as IGang, Bladeburner as IBladeburner, Stanek as IStanek, + Infiltration as IInfiltration, SourceFileLvl, } from "./ScriptEditor/NetscriptDefinitions"; import { NetscriptSingularity } from "./NetscriptFunctions/Singularity"; @@ -96,6 +98,7 @@ interface NS extends INS { gang: IGang; bladeburner: IBladeburner; stanek: IStanek; + infiltration: IInfiltration; } export function NetscriptFunctions(workerScript: WorkerScript): NS { @@ -473,6 +476,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS { const extra = NetscriptExtra(Player, workerScript, helper); const hacknet = NetscriptHacknet(Player, workerScript, helper); const stanek = NetscriptStanek(Player, workerScript, helper); + const infiltration = NetscriptInfiltration(Player, workerScript, helper); const bladeburner = NetscriptBladeburner(Player, workerScript, helper); const codingcontract = NetscriptCodingContract(Player, workerScript, helper); const corporation = NetscriptCorporation(Player, workerScript, helper); @@ -490,6 +494,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS { sleeve: sleeve, corporation: corporation, stanek: stanek, + infiltration: infiltration, ui: ui, formulas: formulas, stock: stockmarket, diff --git a/src/NetscriptFunctions/Infiltration.ts b/src/NetscriptFunctions/Infiltration.ts new file mode 100644 index 000000000..7b2950f42 --- /dev/null +++ b/src/NetscriptFunctions/Infiltration.ts @@ -0,0 +1,75 @@ +import { INetscriptHelper } from "./INetscriptHelper"; +import { IPlayer } from "../PersonObjects/IPlayer"; +import { WorkerScript } from "../Netscript/WorkerScript"; +import { getRamCost } from "../Netscript/RamCostGenerator"; + +import { + Infiltration as IInfiltration, + InfiltrationLocation, + InfiltrationReward, +} from "../ScriptEditor/NetscriptDefinitions"; +import { Location } from "../Locations/Location"; +import { Locations } from "../Locations/Locations"; +import { calculateDifficulty, calculateReward } from "../Infiltration/formulas/game"; +import { + calculateInfiltratorsRepReward, + calculateSellInformationCashReward, + calculateTradeInformationRepReward, +} from "../Infiltration/formulas/victory"; + +export function NetscriptInfiltration( + player: IPlayer, + workerScript: WorkerScript, + helper: INetscriptHelper, +): IInfiltration { + const getLocationsWithInfiltrations = Object.values(Locations).filter( + (location: Location) => location.infiltrationData, + ); + + const calculateInfiltrationData = (location: Location | undefined): InfiltrationLocation => { + if (location === undefined) + throw helper.makeRuntimeErrorMsg( + `infiltration.calculateReward`, + "The provided location does not exist or does not provide infiltrations", + ); + + if (location.infiltrationData === undefined) + throw helper.makeRuntimeErrorMsg( + `infiltration.calculateReward`, + "The provided location does not exist or does not provide infiltrations", + ); + const startingSecurityLevel = location.infiltrationData.startingSecurityLevel; + const difficulty = calculateDifficulty(player, startingSecurityLevel); + const reward = calculateReward(player, startingSecurityLevel); + const maxLevel = location.infiltrationData.maxClearanceLevel; + return { + location: location, + reward: { + tradeRep: calculateTradeInformationRepReward(player, reward, maxLevel, difficulty), + sellCash: calculateSellInformationCashReward(player, reward, maxLevel, difficulty), + infiltratorRep: calculateInfiltratorsRepReward(player, difficulty), + }, + difficulty: difficulty, + }; + }; + return { + calculateDifficulty: function (locationName: string): number { + const location = getLocationsWithInfiltrations.find((infilLocation) => infilLocation.name === locationName); + helper.updateDynamicRam("calculateDifficulty", getRamCost(player, "infiltration", "calculateDifficulty")); + return calculateInfiltrationData(location).difficulty; + }, + calculateRewards: function (locationName: string): InfiltrationReward { + const location = getLocationsWithInfiltrations.find((infilLocation) => infilLocation.name === locationName); + helper.updateDynamicRam("calculateReward", getRamCost(player, "infiltration", "calculateReward")); + return calculateInfiltrationData(location).reward; + }, + getLocations: function (): Location[] { + helper.updateDynamicRam("getLocations", getRamCost(player, "infiltration", "getLocations")); + return getLocationsWithInfiltrations; + }, + getInfiltrations: function (): InfiltrationLocation[] { + helper.updateDynamicRam("getInfiltrations", getRamCost(player, "infiltration", "getInfiltrations")); + return getLocationsWithInfiltrations.map(calculateInfiltrationData); + }, + }; +} diff --git a/src/Script/RamCalculations.ts b/src/Script/RamCalculations.ts index 0bb432581..b27b27e2d 100644 --- a/src/Script/RamCalculations.ts +++ b/src/Script/RamCalculations.ts @@ -17,7 +17,7 @@ import { areImportsEquals } from "../Terminal/DirectoryHelpers"; import { IPlayer } from "../PersonObjects/IPlayer"; export interface RamUsageEntry { - type: 'ns' | 'dom' | 'fn' | 'misc'; + type: "ns" | "dom" | "fn" | "misc"; name: string; cost: number; } @@ -139,7 +139,9 @@ async function parseOnlyRamCalculate( // Finally, walk the reference map and generate a ram cost. The initial set of keys to scan // are those that start with __SPECIAL_INITIAL_MODULE__. let ram = RamCostConstants.ScriptBaseRamCost; - const detailedCosts: RamUsageEntry[] = [{ type: 'misc', name: 'baseCost', cost: RamCostConstants.ScriptBaseRamCost}]; + const detailedCosts: RamUsageEntry[] = [ + { type: "misc", name: "baseCost", cost: RamCostConstants.ScriptBaseRamCost }, + ]; const unresolvedRefs = Object.keys(dependencyMap).filter((s) => s.startsWith(initialModule)); const resolvedRefs = new Set(); while (unresolvedRefs.length > 0) { @@ -149,19 +151,19 @@ async function parseOnlyRamCalculate( // Check if this is one of the special keys, and add the appropriate ram cost if so. if (ref === "hacknet" && !resolvedRefs.has("hacknet")) { ram += RamCostConstants.ScriptHacknetNodesRamCost; - detailedCosts.push({ type: 'ns', name: 'hacknet', cost: RamCostConstants.ScriptHacknetNodesRamCost}); + detailedCosts.push({ type: "ns", name: "hacknet", cost: RamCostConstants.ScriptHacknetNodesRamCost }); } if (ref === "document" && !resolvedRefs.has("document")) { ram += RamCostConstants.ScriptDomRamCost; - detailedCosts.push({ type: 'dom', name: 'document', cost: RamCostConstants.ScriptDomRamCost}); + detailedCosts.push({ type: "dom", name: "document", cost: RamCostConstants.ScriptDomRamCost }); } if (ref === "window" && !resolvedRefs.has("window")) { ram += RamCostConstants.ScriptDomRamCost; - detailedCosts.push({ type: 'dom', name: 'window', cost: RamCostConstants.ScriptDomRamCost}); + detailedCosts.push({ type: "dom", name: "window", cost: RamCostConstants.ScriptDomRamCost }); } if (ref === "corporation" && !resolvedRefs.has("corporation")) { ram += RamCostConstants.ScriptCorporationRamCost; - detailedCosts.push({ type: 'ns', name: 'corporation', cost: RamCostConstants.ScriptCorporationRamCost}); + detailedCosts.push({ type: "ns", name: "corporation", cost: RamCostConstants.ScriptCorporationRamCost }); } resolvedRefs.add(ref); @@ -203,7 +205,7 @@ async function parseOnlyRamCalculate( // This accounts for namespaces (Bladeburner, CodingCpntract, etc.) let func; - let refDetail = 'n/a'; + let refDetail = "n/a"; if (ref in workerScript.env.vars.bladeburner) { func = workerScript.env.vars.bladeburner[ref]; refDetail = `bladeburner.${ref}`; @@ -213,6 +215,9 @@ async function parseOnlyRamCalculate( } else if (ref in workerScript.env.vars.stanek) { func = workerScript.env.vars.stanek[ref]; refDetail = `stanek.${ref}`; + } else if (ref in workerScript.env.vars.infiltration) { + func = workerScript.env.vars.infiltration[ref]; + refDetail = `infiltration.${ref}`; } else if (ref in workerScript.env.vars.gang) { func = workerScript.env.vars.gang[ref]; refDetail = `gang.${ref}`; @@ -231,12 +236,12 @@ async function parseOnlyRamCalculate( } const fnRam = applyFuncRam(func); ram += fnRam; - detailedCosts.push({ type: 'fn', name: refDetail, cost: fnRam}); + detailedCosts.push({ type: "fn", name: refDetail, cost: fnRam }); } catch (error) { continue; } } - return { cost: ram, entries: detailedCosts.filter(e => e.cost > 0) }; + return { cost: ram, entries: detailedCosts.filter((e) => e.cost > 0) }; } catch (error) { // console.info("parse or eval error: ", error); // This is not unexpected. The user may be editing a script, and it may be in diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index a062ddd29..fa03ba50c 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -1,3 +1,5 @@ +import { Location } from "src/Locations/Location"; + /** * @public */ @@ -4119,6 +4121,59 @@ interface Stanek { remove(rootX: number, rootY: number): boolean; } +export interface InfiltrationReward { + tradeRep: number; + sellCash: number; + infiltratorRep: number; +} + +export interface InfiltrationLocation { + location: Location; + reward: InfiltrationReward; + difficulty: number; +} + +/** + * Infiltration API. + * @public + */ +interface Infiltration { + /** + * Calculate the difficulty of performing an infiltration. + * @remarks + * RAM cost: 2.5 GB + * + * @param locationName - name of the location to check. + * @returns the difficulty. + */ + calculateDifficulty(locationName: string): number; + /** + * Calculate the rewards for trading and selling information for completing an infiltration. + * @remarks + * RAM cost: 2.5 GB + * + * @param locationName - name of the location to check. + * @returns the trade reputation, sell cash and infiltrators rep reward. + */ + calculateRewards(locationName: string): InfiltrationReward; + /** + * Get all locations that can be infiltrated. + * @remarks + * RAM cost: 5 GB + * + * @returns all locations that can be infiltrated. + */ + getLocations(): Location[]; + /** + * Get all infiltrations with difficulty, location and rewards. + * @remarks + * RAM cost: 15 GB + * + * @returns all infiltrations with difficulty, location and rewards. + */ + getInfiltrations(): InfiltrationLocation[]; +} + /** * User Interface API. * @public @@ -4268,6 +4323,11 @@ export interface NS extends Singularity { * RAM cost: 0 GB */ readonly stanek: Stanek; + /** + * Namespace for infiltration functions. + * RAM cost: 0 GB + */ + readonly infiltration: Infiltration; /** * Namespace for corporation functions. * RAM cost: 0 GB