DNET: Player feedback (#2545)

This commit is contained in:
Michael Ficocelli
2026-03-19 21:07:43 -07:00
committed by GitHub
parent 73b7921ef0
commit 1b6b07faae
38 changed files with 248 additions and 108 deletions

View File

@@ -6,10 +6,12 @@
Sends a network request to try to authenticate on a darkweb server. The target server must be directly connected to the server that the script is running on. The speed of authentication scales with the number of threads used.
If successful, grants the script a session, allowing it to exec() scripts on that server, or scp() files to it. (scp() \*from\* the server is always allowed.)
If successful, grants the current script a session, allowing it to exec() scripts on that server, or scp() files to it. (scp() \*from\* the server is always allowed.)
Note that the charisma level on a server is not a requirement for authentication, but authentication takes longer if the player's charisma is below the server's charisma level.
Note that the session granted is only for the current script instance (by PID) - other running scripts will need to use connectToSession with the correct password to also get a session with the target server.
**Signature:**
```typescript
@@ -92,5 +94,5 @@ A promise that resolves to a [DarknetResult](./bitburner.darknetresult.md) objec
## Remarks
RAM cost: 0.6 GB
RAM cost: 0.4 GB

View File

@@ -6,10 +6,12 @@
Attempts to connect to a target darkweb server that you have previously authenticated on. Unlike `authenticate`<!-- -->, connectToSession can be used to get a session on servers at any distance.
If successful, grants the script a session, allowing it to scp() files from that target. It also allows starting scripts with exec() on that target, if the target is directly connected to the server that the script is running on, or has a backdoor or stasis link.
If successful, grants the script a session, allowing it to scp() files to that target. It also allows starting scripts with exec() on that target, if the target is directly connected to the server that the script is running on, or has a backdoor or stasis link.
If unsuccessful, more detail may be able to be gathered by using heartbleed() to look at the resulting logs on the server.
Note that the session granted is only for the current script instance (by PID) - other running scripts will need to use connectToSession with the correct password to also get a session with the target server.
**Signature:**
```typescript

View File

@@ -34,10 +34,12 @@ Description
Sends a network request to try to authenticate on a darkweb server. The target server must be directly connected to the server that the script is running on. The speed of authentication scales with the number of threads used.
If successful, grants the script a session, allowing it to exec() scripts on that server, or scp() files to it. (scp() \*from\* the server is always allowed.)
If successful, grants the current script a session, allowing it to exec() scripts on that server, or scp() files to it. (scp() \*from\* the server is always allowed.)
Note that the charisma level on a server is not a requirement for authentication, but authentication takes longer if the player's charisma is below the server's charisma level.
Note that the session granted is only for the current script instance (by PID) - other running scripts will need to use connectToSession with the correct password to also get a session with the target server.
</td></tr>
<tr><td>
@@ -49,10 +51,12 @@ Note that the charisma level on a server is not a requirement for authentication
Attempts to connect to a target darkweb server that you have previously authenticated on. Unlike `authenticate`<!-- -->, connectToSession can be used to get a session on servers at any distance.
If successful, grants the script a session, allowing it to scp() files from that target. It also allows starting scripts with exec() on that target, if the target is directly connected to the server that the script is running on, or has a backdoor or stasis link.
If successful, grants the script a session, allowing it to scp() files to that target. It also allows starting scripts with exec() on that target, if the target is directly connected to the server that the script is running on, or has a backdoor or stasis link.
If unsuccessful, more detail may be able to be gathered by using heartbleed() to look at the resulting logs on the server.
Note that the session granted is only for the current script instance (by PID) - other running scripts will need to use connectToSession with the correct password to also get a session with the target server.
</td></tr>
<tr><td>

View File

@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Multipliers](./bitburner.multipliers.md) &gt; [dnet\_money](./bitburner.multipliers.dnet_money.md)
## Multipliers.dnet\_money property
Multiplier to amount of money gained from phishing and caches on darknet servers
**Signature:**
```typescript
dnet_money: number;
```

View File

@@ -318,6 +318,25 @@ number
Multiplier to dexterity skill
</td></tr>
<tr><td>
[dnet\_money](./bitburner.multipliers.dnet_money.md)
</td><td>
</td><td>
number
</td><td>
Multiplier to amount of money gained from phishing and caches on darknet servers
</td></tr>
<tr><td>

View File

@@ -40,6 +40,7 @@ export interface AugmentationCtorParams {
faction_rep?: number;
crime_money?: number;
crime_success?: number;
dnet_money?: number;
work_money?: number;
hacknet_node_money?: number;
hacknet_node_purchase_cost?: number;

View File

@@ -1859,9 +1859,11 @@ export const Augmentations: Record<AugmentationName, Augmentation> = (() => {
"Created by a mysterious figure known only as 'The Sculptor', this augmentation appears as a set of silvery " +
"metallic patterns on the user's upper back and shoulders. " +
"Awarded to those who discover the secrets of the labyrinth.",
stats: "This augmentation increases the stasis link limit by one, and raises charisma by 15% and agility by 10%.",
charisma: 1.15,
stats:
"This augmentation increases the stasis link limit by one, and raises charisma by 5% and agility by 10%, and darknet money by 30%.",
charisma: 1.05,
agility: 1.1,
dnet_money: 1.3,
isSpecial: true,
factions: [],
},
@@ -1873,9 +1875,9 @@ export const Augmentations: Record<AugmentationName, Augmentation> = (() => {
"Its creator, the enigmatic Sculptor, refuses to reveal the details of how it works, and only mutters about 'liveware APIs'. " +
"Awarded to those who discover the secrets of the labyrinth.",
stats:
"This augmentation increases the speed of authentication and heartbleed by 20%, and raises charisma and dexterity by 20%.",
charisma: 1.2,
dexterity: 1.2,
"This augmentation increases the speed of authentication and heartbleed by 20%, and raises charisma and dexterity by 6%.",
charisma: 1.06,
dexterity: 1.06,
isSpecial: true,
prereqs: [AugmentationName.TheBrokenWings],
factions: [],
@@ -1888,9 +1890,10 @@ export const Augmentations: Record<AugmentationName, Augmentation> = (() => {
"Appearing as a simple insignia on the user's forarm, its true function is unknown. It is said to be one of the tools of The Sculptor. " +
"Awarded to those who discover the secrets of the labyrinth.",
stats:
"This augmentation increases the stasis link limit by one, and raises charisma by 35% and strength by 25%.",
charisma: 1.35,
strength: 1.25,
"This augmentation increases the stasis link limit by one, and raises charisma by 7%, strength by 10%, and darknet money by 10%.",
charisma: 1.07,
strength: 1.1,
dnet_money: 1.1,
isSpecial: true,
prereqs: [AugmentationName.TheBoots],
factions: [],
@@ -1902,9 +1905,10 @@ export const Augmentations: Record<AugmentationName, Augmentation> = (() => {
"This skeletal augmentation greatly enhances the users durability and health. Inspired by the original Staff of Medicine that is said to " +
"have been given to Daedalus as a reward for the completion of the Labyrinth, which all modern augments are a descendant of. ",
stats:
"This augmentation increases the stasis link limit by one, and raises charisma xp by 20% and defense by 30%.",
charisma_exp: 1.2,
defense: 1.3,
"This augmentation increases the stasis link limit by one, and raises charisma xp, defense, and darknet money by 10%.",
charisma_exp: 1.1,
defense: 1.1,
dnet_money: 1.1,
isSpecial: true,
prereqs: [AugmentationName.TheHammer],
factions: [],
@@ -1917,10 +1921,10 @@ export const Augmentations: Record<AugmentationName, Augmentation> = (() => {
"This augmentation enhances the user's ability to assess probabilities, predict outcomes, and adapt strategies in real-time, " +
"making them exceptionally persuasive and confident in negotiations and social interactions. " +
"Awarded to those who discover the secrets of the labyrinth.",
stats: "This augmentation raises charisma by 40%, hacking by 10%, and company rep by 5%.",
charisma: 1.4,
hacking: 1.1,
stats: "This augmentation raises charisma by 9%, company rep by 5%, and darknet money by 15%.",
charisma: 1.09,
company_rep: 1.05,
dnet_money: 1.15,
isSpecial: true,
prereqs: [AugmentationName.TheStaff],
factions: [],
@@ -1934,10 +1938,11 @@ export const Augmentations: Record<AugmentationName, Augmentation> = (() => {
"turning every interaction into a calculated success. The technique is sometimes referred to as Solomonoff's Lightsaber, as it is a " +
"more powerful version of Occam's razor. " +
"The final augment awarded to those who discover the secrets of the labyrinth.",
stats: "This augmentation raises charisma by 50%, hacking by 16%, and company rep by 10%.",
charisma: 1.5,
hacking: 1.16,
stats: "This augmentation raises charisma, hacking, darknet money, and company rep by 10%.",
charisma: 1.1,
hacking: 1.1,
company_rep: 1.1,
dnet_money: 1.1,
isSpecial: true,
prereqs: [AugmentationName.TheLaw],
factions: [],

View File

@@ -263,6 +263,13 @@ export function PlayerMultipliers(): React.ReactElement {
bnMult: currentNodeMults.CrimeMoney,
color: Settings.theme.money,
},
{
mult: "Darknet Money",
current: Player.mults.dnet_money,
augmented: Player.mults.dnet_money * mults.dnet_money,
bnMult: currentNodeMults.DarknetMoneyMultiplier,
color: Settings.theme.money,
},
];
if (Player.canAccessBladeburner() && currentNodeMults.BladeburnerRank > 0) {

View File

@@ -625,6 +625,8 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier
StaneksGiftPowerMultiplier: 0.75,
StaneksGiftExtraSize: -2,
DarknetMoneyMultiplier: 0.4,
WorldDaemonDifficulty: 2,
});
}
@@ -653,6 +655,8 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier
StaneksGiftPowerMultiplier: 1.5,
StaneksGiftExtraSize: 0,
DarknetMoneyMultiplier: 0.4,
WorldDaemonDifficulty: 3,
});
}
@@ -682,6 +686,8 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier
StaneksGiftPowerMultiplier: 1.3,
StaneksGiftExtraSize: 0,
DarknetMoneyMultiplier: 0.7,
WorldDaemonDifficulty: 1.5,
});
}
@@ -830,6 +836,8 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier
StaneksGiftPowerMultiplier: 0.5,
StaneksGiftExtraSize: 2,
DarknetMoneyMultiplier: 0.5,
WorldDaemonDifficulty: 2,
});
}
@@ -873,6 +881,8 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier
StaneksGiftPowerMultiplier: 0.75,
StaneksGiftExtraSize: -3,
DarknetMoneyMultiplier: 0.4,
WorldDaemonDifficulty: 2,
});
}
@@ -946,6 +956,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier
ManualHackMoney: dec,
ScriptHackMoney: dec,
CodingContractMoney: dec,
DarknetMoneyMultiplier: dec,
ClassGymExpGain: dec,
CompanyWorkExpGain: dec,
@@ -1025,6 +1036,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier
StaneksGiftPowerMultiplier: 2,
StaneksGiftExtraSize: 1,
DarknetMoneyMultiplier: 0.5,
WorldDaemonDifficulty: 3,
});

View File

@@ -55,6 +55,9 @@ export class CodingContract {
processed outside of this file */
reward: ICodingContractReward | null;
/* Scalar for the reward, used to generate lower-value CCTs more frequently */
rewardScaling: number = 1;
/* Number of times the Contract has been attempted */
tries = 0;
@@ -65,6 +68,7 @@ export class CodingContract {
fn = "default.cct",
type = CodingContractName.FindLargestPrimeFactor,
reward: ICodingContractReward | null = null,
rewardScaling: number = 1,
) {
const path = resolveContractFilePath(fn);
if (!path) {
@@ -78,6 +82,7 @@ export class CodingContract {
this.type = type;
this.state = CodingContractTypes[type].generate();
this.reward = reward;
this.rewardScaling = rewardScaling;
}
getAnswer() {

View File

@@ -130,6 +130,7 @@ interface IGenerateContractParams {
server?: string;
filename?: ContractFilePath;
reward?: ICodingContractReward;
rewardScaling?: number;
}
export function generateContract(params: IGenerateContractParams): void {
@@ -163,7 +164,7 @@ export function generateContract(params: IGenerateContractParams): void {
if (filename == null) {
return;
}
const contract = new CodingContract(filename, problemType, reward);
const contract = new CodingContract(filename, problemType, reward, params.rewardScaling);
server.addContract(contract);
}

View File

@@ -80,6 +80,7 @@ export function initDarkwebServer(): void {
darkweb.isStationary = true;
darkweb.hasAdminRights = true;
darkweb.blockedRam = 0;
darkweb.maxRam = 16;
darkweb.scripts = scripts;
darkweb.contracts = contracts;
if (hasTOR) {

View File

@@ -17,7 +17,7 @@ import {
getAllDarknetServers,
getAllMovableDarknetServers,
getAllOpenPositions,
getBackdooredDarkwebServers,
getBackdooredDarknetServers,
getDarknetCyclesPerMutation,
getIslands,
} from "../utils/darknetNetworkUtils";
@@ -89,7 +89,7 @@ export const mutateDarknet = (): void => {
}
if (Math.random() < 0.1) {
const backdooredServers = getBackdooredDarkwebServers();
const backdooredServers = getBackdooredDarknetServers();
const server = backdooredServers[Math.floor(Math.random() * backdooredServers.length)];
if (server) {
restartServer(server);
@@ -98,7 +98,7 @@ export const mutateDarknet = (): void => {
}
if (Math.random() < 0.05) {
const backdooredServers = getBackdooredDarkwebServers();
const backdooredServers = getBackdooredDarknetServers();
const server = backdooredServers[Math.floor(Math.random() * backdooredServers.length)];
if (server) {
deleteDarknetServer(server);

View File

@@ -1,27 +1,26 @@
import { tryGeneratingRandomContract } from "../../CodingContract/ContractGenerator";
import { generateContract } from "../../CodingContract/ContractGenerator";
import { Player } from "@player";
import { formatMoney, formatNumber } from "../../ui/formatNumber";
import { formatMoney } from "../../ui/formatNumber";
import { getLabAugReward, isLabyrinthServer, LAB_CACHE_NAME } from "./labyrinth";
import { SnackbarEvents } from "../../ui/React/Snackbar";
import { AugmentationName, CompletedProgramName, ToastVariant } from "@enums";
import { AugmentationName, CompletedProgramName, StockSymbol, ToastVariant } from "@enums";
import { currentNodeMults } from "../../BitNode/BitNodeMultipliers";
import { CreateProgramWork } from "../../Work/CreateProgramWork";
import { initStockMarket, isStockMarketInitialized } from "../../StockMarket/StockMarket";
import { initStockMarket, isStockMarketInitialized, StockMarket } from "../../StockMarket/StockMarket";
import { cachePrefixes } from "../models/dictionaryData";
import type { DarknetServer } from "../../Server/DarknetServer";
import { type CacheFilePath, resolveCacheFilePath } from "../../Paths/CacheFilePath";
import type { CacheResult, Result } from "@nsdefs";
import { resolveCacheFilePath } from "../../Paths/CacheFilePath";
import type { CacheResult } from "@nsdefs";
import { addClue, cctCooldownReached } from "./effects";
export const generateCacheFilename: (prefix?: string) => CacheFilePath | null = (prefix) => {
export const generateCacheFilename = (isPhishingCache: boolean, prefix?: string) => {
const filenamePrefix = prefix ?? cachePrefixes[Math.floor(Math.random() * cachePrefixes.length)];
return resolveCacheFilePath(`${filenamePrefix}_${Math.random().toString().substring(2, 5)}.cache`);
const suffix = isPhishingCache ? ".d.cache" : ".cache";
return resolveCacheFilePath(`${filenamePrefix}_${Math.random().toString().substring(2, 5)}${suffix}`);
};
export const addCacheToServer: (server: DarknetServer, prefix?: string) => Result<{ cacheFilename: CacheFilePath }> = (
server,
prefix,
) => {
const cacheFilename = generateCacheFilename(prefix);
export const addCacheToServer = (server: DarknetServer, isPhishingCache: boolean, prefix?: string) => {
const cacheFilename = generateCacheFilename(isPhishingCache, prefix);
if (!cacheFilename) {
return { success: false, message: `Cannot generate path. prefix: ${prefix}` };
}
@@ -42,9 +41,13 @@ export const getRewardFromCache = (server: DarknetServer, cacheName: string, sup
};
}
const rewards = [getMoneyReward, getXpReward, getProgramAndStockMarketRelatedRewards, getCCTReward];
const rewards = [getMoneyReward, getProgramAndStockMarketRelatedRewards, getStockReward, getDataFileReward];
if (cacheName.endsWith(".d.cache")) {
// only include ccts from caches generated from phishing attacks
rewards.push(getCCTReward);
}
const reward = rewards[Math.floor(Math.random() * rewards.length)];
const result = reward(difficulty);
const result = reward(difficulty, server);
if (!suppressToast) {
SnackbarEvents.emit(
@@ -62,16 +65,17 @@ export const getRewardFromCache = (server: DarknetServer, cacheName: string, sup
};
};
export const getCCTReward = (difficulty: number): string => {
if (Math.random() < difficulty * 0.2) {
export const getCCTReward = (difficulty: number, server: DarknetServer): string => {
if (!cctCooldownReached()) {
return getMoneyReward(difficulty);
}
const contractCount = Math.floor(Math.min(20, difficulty) * 0.2 - 1.5 + Math.random() * 3);
if (contractCount <= 0) {
const contractCount = Math.min(Math.floor(Math.min(20, difficulty) * 0.1 - 1.5 + Math.random() * 3), 3);
if (contractCount < 1) {
return getMoneyReward(difficulty);
}
tryGeneratingRandomContract(contractCount);
for (let i = 0; i < contractCount; i++) {
generateContract({ server: server.hostname, rewardScaling: 1 / 5 });
}
return `New coding contracts are now available on the network!`;
};
@@ -83,16 +87,32 @@ export const getMoneyReward = (difficulty: number): string => {
((200 + Player.skills.charisma) / 200) *
sf15_3Factor *
Player.mults.crime_money *
Player.mults.dnet_money *
currentNodeMults.DarknetMoneyMultiplier; // TODO: adjust balance
Player.gainMoney(reward, "darknet");
return `You have discovered a cache with ${formatMoney(reward)}.`;
};
export const getXpReward = (difficulty: number): string => {
const sf15_3Factor = Player.activeSourceFileLvl(15) > 3 ? 1.5 : 1;
const reward = 1.2 ** difficulty * 500 * sf15_3Factor * Player.mults.charisma_exp; // TODO: adjust balance
Player.gainCharismaExp(reward);
return `You have discovered a cache with ${formatNumber(reward, 0)} cha XP.`;
export const getStockReward = (difficulty: number): string => {
if (!isStockMarketInitialized()) {
initStockMarket();
}
const stockSymbols = Object.keys(StockSymbol);
const randomStock = stockSymbols[Math.floor(Math.random() * stockSymbols.length)];
const shares = Math.floor(1 + difficulty * 5 + Math.random() * 10);
StockMarket[randomStock].playerShares += shares;
return `You have discovered a stock option cache containing ${shares} shares of ${randomStock}!`;
};
export const getDataFileReward = (difficulty: number, server: DarknetServer): string => {
const currentDataFiles = server.textFiles.size;
addClue(server);
addClue(server);
const dataFilesGained = server.textFiles.size - currentDataFiles;
if (dataFilesGained === 0) {
return getMoneyReward(difficulty);
}
return `You have discovered a data file cache!`;
};
export const getProgramAndStockMarketRelatedRewards = (difficulty: number): string => {
@@ -135,7 +155,7 @@ export const getProgramAndStockMarketRelatedRewards = (difficulty: number): stri
return `You have discovered a cache of stolen 4S Data!`;
}
return getXpReward(difficulty);
return getMoneyReward(difficulty);
};
const getLabReward = (): string => {

View File

@@ -1,7 +1,6 @@
import { Player } from "@player";
import type { DarknetServerData, Person as IPerson } from "@nsdefs";
import { AugmentationName, CompletedProgramName, LiteratureName } from "@enums";
import { generateContract } from "../../CodingContract/ContractGenerator";
import {
commonPasswordDictionary,
notebookFileNames,
@@ -20,7 +19,7 @@ import { populateDarknet } from "../controllers/NetworkGenerator";
import { getDarknetServer } from "../utils/darknetServerUtils";
import {
getAllMovableDarknetServers,
getBackdooredDarkwebServers,
getBackdooredDarknetServers,
getNearbyNonEmptyPasswordServer,
getStasisLinkServers,
} from "../utils/darknetNetworkUtils";
@@ -41,14 +40,9 @@ export const handleSuccessfulAuth = (server: DarknetServer, threads: number, pid
server.hasAdminRights = true;
addClue(server);
const cctChance = Math.min(0.12, 0.02 * (server.difficulty - 1));
if (Math.random() < cctChance) {
generateContract({ server: server.hostname });
}
const chance = 0.1 * 1.05 ** server?.difficulty;
if (Math.random() < chance && !isLabyrinthServer(server.hostname)) {
addCacheToServer(server);
addCacheToServer(server, false);
}
};
@@ -110,7 +104,7 @@ export const calculateAuthenticationTime = (
};
export const getBackdoorAuthTimeDebuff = () => {
const backdooredServerCount = getBackdooredDarkwebServers().length;
const backdooredServerCount = getBackdooredDarknetServers().length;
const serverCount = getAllMovableDarknetServers().filter((s) => s.hasAdminRights).length;
const safeBackdoors = Math.max(serverCount / (NET_WIDTH * 3), 2);
const backdoorSurplus = Math.max(0, backdooredServerCount - safeBackdoors);
@@ -132,7 +126,7 @@ export const getMultiplierFromCharisma = (scalar = 1) => {
export const calculatePasswordAttemptChaGain = (server: DarknetServerData, threads: number = 1, success = false) => {
const baseXpGain = 3;
const difficultyBase = 0.8;
const difficultyBase = 1.1;
const xpGain = baseXpGain + difficultyBase ** server.difficulty;
const alreadyHackedMult = server.hasAdminRights ? 0.2 : 1;
const successMult = success && !server.hasAdminRights ? 10 : 1;
@@ -264,7 +258,7 @@ export const setStasisLink = (ctx: NetscriptContext, server: DarknetServer, shou
export const chargeServerMigration = (server: DarknetServer, threads = 1) => {
const chargeIncrease = ((Player.skills.charisma + 500) / (server.difficulty * 200 + 1000)) * 0.01 * threads;
const xpGained = Player.mults.charisma_exp * 50 * ((200 + Player.skills.charisma) / 200) * threads;
const xpGained = Player.mults.charisma_exp * 5 * threads * server.difficulty;
Player.gainCharismaExp(xpGained);
const currentCharge = DarknetState.migrationInductionServers.get(server.hostname) ?? 0;
const newCharge = Math.min(currentCharge + chargeIncrease, 1);
@@ -292,4 +286,9 @@ export const getDarkscapeNavigator = () => {
populateDarknet();
};
export const cctCooldownReached = () => {
const timeSinceLastCCT = new Date().getTime() - DarknetState.lastCctRewardTime.getTime();
return timeSinceLastCCT > 10 * 60 * 1000;
};
export const hasFullDarknetAccess = (): boolean => Player.bitNodeN === 15 || Player.activeSourceFileLvl(15) > 0;

View File

@@ -303,7 +303,7 @@ export const handleLabyrinthPassword = (
server.hasAdminRights = true;
const cacheCount = getLabyrinthDetails().name === SpecialServers.BonusLab ? 3 : 1;
for (let i = 0; i < cacheCount; i++) {
addCacheToServer(server, LAB_CACHE_NAME);
addCacheToServer(server, false, LAB_CACHE_NAME);
}
addSessionToServer(labServer, pid);

View File

@@ -6,7 +6,7 @@ import { errorMessage } from "../../Netscript/ErrorMessages";
import type { BaseServer } from "../../Server/BaseServer";
import { GetServer } from "../../Server/AllServers";
import { GenericResponseMessage, ResponseCodeEnum } from "../Enums";
import { getBackdooredDarkwebServers } from "../utils/darknetNetworkUtils";
import { getBackdooredDarknetServers } from "../utils/darknetNetworkUtils";
import { hasDarknetAccess } from "../utils/darknetAuthUtils";
import { DarknetServer } from "../../Server/DarknetServer";
import { CompletedProgramName } from "../../Enums";
@@ -149,6 +149,6 @@ export function expectRunningOnDarknetServer(ctx: NetscriptContext): DarknetServ
}
export function getTimeoutChance() {
const backdooredDarknetServerCount = getBackdooredDarkwebServers().length - 2;
const backdooredDarknetServerCount = getBackdooredDarknetServers().length - 2;
return Math.max(Math.min(backdooredDarknetServerCount * 0.03, 0.5), 0);
}

View File

@@ -10,23 +10,20 @@ import { ResponseCodeEnum } from "../Enums";
import { isLabyrinthServer } from "./labyrinth";
export const getPhishingAttackSpeed = () => Math.max(10000 * (400 / (400 + Player.skills.charisma)), 200);
const getPhishingCacheCooldownDuration = () => (hasDarknetBonusTime() ? 12_000 : 24_000);
export const handlePhishingAttack = (ctx: NetscriptContext, server: DarknetServer) => {
const threads = ctx.workerScript.scriptRef.threads;
const xpGained = Player.mults.charisma_exp * threads * 50 * ((200 + Player.skills.charisma) / 200);
Player.gainCharismaExp(xpGained);
const xpReward = Player.mults.charisma_exp * threads * 50;
const timeSinceLastRewardCache = new Date().getTime() - DarknetState.lastPhishingCacheTime.getTime();
const rewardCacheChance = 0.005 * Player.mults.crime_success * threads * ((400 + Player.skills.charisma) / 400);
const moneyRewardChance = 0.05 * Player.mults.crime_success * ((100 + Player.skills.charisma) / 100);
const cooldown = getPhishingCacheCooldownDuration();
const moneyRewardChance = 0.05 * Player.mults.crime_success * ((200 + Player.skills.charisma) / 200);
const isLabServer = isLabyrinthServer(server.hostname);
if (timeSinceLastRewardCache > cooldown && Math.random() < rewardCacheChance && !isLabServer) {
addCacheToServer(server);
if (phishingCacheCooldownReached() && Math.random() < rewardCacheChance && !isLabServer) {
addCacheToServer(server, true);
Player.gainCharismaExp(xpReward);
DarknetState.lastPhishingCacheTime = new Date();
const result = `Phishing attack succeeded! Found a cache file. (Gained ${formatNumber(xpGained, 1)} cha xp)`;
const result = `Phishing attack succeeded! Found a cache file. (Gained ${formatNumber(xpReward, 1)} cha xp)`;
helpers.log(ctx, () => result);
return {
success: true,
@@ -38,17 +35,19 @@ export const handlePhishingAttack = (ctx: NetscriptContext, server: DarknetServe
const bonusTimeFactor = hasDarknetBonusTime() ? 1.3 : 1;
const depthFactor = 0.1 + server.depth * 0.05;
const moneyReward =
2000 *
500 *
Player.mults.crime_money *
Player.mults.dnet_money *
depthFactor *
threads *
((200 + Player.skills.charisma) / 200) *
((400 + Player.skills.charisma) / 400) *
bonusTimeFactor *
randomFactor *
currentNodeMults.DarknetMoneyMultiplier;
Player.gainMoney(moneyReward, "darknet");
Player.gainCharismaExp(xpReward);
const result = `Phishing attack succeeded! $${formatNumber(moneyReward, 2)} retrieved. (Gained ${formatNumber(
xpGained,
xpReward,
1,
)} cha xp)`;
helpers.log(ctx, () => result);
@@ -58,7 +57,8 @@ export const handlePhishingAttack = (ctx: NetscriptContext, server: DarknetServe
message: result,
};
}
const result = `There were no takers on that phishing attempt. (Gained ${formatNumber(xpGained, 1)} cha xp)`;
Player.gainCharismaExp(xpReward / 4);
const result = `There were no takers on that phishing attempt. (Gained ${formatNumber(xpReward / 4, 1)} cha xp)`;
helpers.log(ctx, () => result);
return {
success: false,
@@ -66,3 +66,8 @@ export const handlePhishingAttack = (ctx: NetscriptContext, server: DarknetServe
message: result,
};
};
const phishingCacheCooldownReached = () => {
const timeSinceLastRewardCache = new Date().getTime() - DarknetState.lastPhishingCacheTime.getTime();
return timeSinceLastRewardCache > 3 * 60 * 1000; // 3 minutes;
};

View File

@@ -27,8 +27,7 @@ export const handleRamBlockRemoved = (ctx: NetscriptContext, server: DarknetServ
if (server.blockedRam <= 0) {
handleRamBlockClearedRewards(server);
}
const xpGained =
Player.mults.charisma_exp * threads * 10 * 1.1 ** difficulty * ((200 + Player.skills.charisma) / 200);
const xpGained = Player.mults.charisma_exp * threads * 10 * 1.1 ** difficulty;
Player.gainCharismaExp(xpGained);
const result = `Liberated ${formatNumber(
@@ -48,7 +47,7 @@ export const handleRamBlockRemoved = (ctx: NetscriptContext, server: DarknetServ
*/
export const handleRamBlockClearedRewards = (server: DarknetServer) => {
if (!isLabyrinthServer(server.hostname)) {
addCacheToServer(server);
addCacheToServer(server, false);
}
if (Math.random() < 0.3) {
addClue(server);

View File

@@ -17,6 +17,7 @@ import { getRamBlock } from "../effects/ramblock";
import { hasFullDarknetAccess } from "../effects/effects";
import { getFriendlyType, TypeAssertionError } from "../../utils/TypeAssertion";
import { isIPAddress } from "../../Types/strings";
import { roundToTwo } from "../../utils/helpers/roundToTwo";
export type PasswordResponse = {
code: DarknetResponseCode;
@@ -58,7 +59,7 @@ export type DarknetServerOptions = {
};
export const DnetServerBuilder = (options: DarknetServerOptions): DarknetServer => {
const maxRam = 16 * 2 ** Math.floor(options.difficulty / 4);
const maxRam = getMaxRam(options.difficulty);
const ramBlock = options.preventBlockedRam ? 0 : getRamBlock(maxRam);
const name = options.name ?? generateDarknetServerName();
@@ -192,3 +193,10 @@ const l33tifyName = (name: string): string => {
}
return updatedName;
};
const getMaxRam = (difficulty: number): number => {
const baseRam = 16 * 2 ** Math.floor(difficulty / 6);
const sizeMutations = [0.5, 1, 1, 1.15, 1.4];
const mutation = sizeMutations[Math.floor(Math.random() * sizeMutations.length)];
return roundToTwo(Math.max(baseRam * mutation, 16));
};

View File

@@ -47,6 +47,7 @@ export const DarknetState = {
labLocations: { "-1": [1, 1] } as Record<number, [number, number] | undefined>,
lastPhishingCacheTime: new Date(),
lastCctRewardTime: new Date(),
lastStormTime: new Date(),
stockPromotions: {} as Record<string, number>,

View File

@@ -14,7 +14,7 @@ import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
const MAX_LOG_LINES = 200;
export const capturePackets = (server: DarknetServer) => {
const BASE_PASSWORD_INCLUSION_RATE = 0.18;
const BASE_PASSWORD_INCLUSION_RATE = 0.12;
const DIFFICULTY_MODIFIER = 0.88;
const difficulty = server.difficulty * 1.3;
const vulnerability = server.modelId === ModelIds.packetSniffer ? 8 : 1;

View File

@@ -252,7 +252,7 @@ export function NetworkDisplayWrapper(): React.ReactElement {
<Typography variant={"h5"} sx={{ fontWeight: "bold" }}>
Dark Net
</Typography>
{instability && (
{instability > 0 && (
<Tooltip
title={
<>
@@ -299,13 +299,13 @@ export function NetworkDisplayWrapper(): React.ReactElement {
{DarknetState.Network.slice(0, netDisplayDepth).map((row) =>
row.map(
(server) =>
server && (
!!server && (
<ServerStatusBox server={server} key={server.ip} enableAuth={allowAuth(server)} classes={classes} />
),
),
)}
{labyrinth && netDisplayDepth > depth && (
{!!labyrinth && netDisplayDepth > depth && (
<ServerStatusBox server={labyrinth} enableAuth={allowAuth(labyrinth)} classes={classes} />
)}
</div>

View File

@@ -149,7 +149,7 @@ export const PasswordPrompt = ({ server, onClose }: PasswordPromptProps): React.
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
<span className={classes.serverDetailsText}>Hint:</span> {server.staticPasswordHint}
<br />
{server.passwordHintData && (
{!!server.passwordHintData && (
<>
<span className={classes.serverDetailsText}>Data: </span> {server.passwordHintData}
<br />

View File

@@ -86,7 +86,7 @@ export const getAllAdjacentNeighbors = (x: number, y: number): DarknetServer[] =
export const getIslands = () => getAllMovableDarknetServers().filter((s) => !s.serversOnNetwork.length);
export const getBackdooredDarkwebServers = (): DarknetServer[] =>
export const getBackdooredDarknetServers = (): DarknetServer[] =>
getAllMovableDarknetServers().filter((s) => !s.hasStasisLink && s.backdoorInstalled);
export const getNearbyNonEmptyPasswordServer = (server: DarknetServer, disconnected = false) => {

View File

@@ -950,6 +950,7 @@ import nsDoc_bitburner_multipliers_defense_exp_md from "../../markdown/bitburner
import nsDoc_bitburner_multipliers_defense_md from "../../markdown/bitburner.multipliers.defense.md?raw";
import nsDoc_bitburner_multipliers_dexterity_exp_md from "../../markdown/bitburner.multipliers.dexterity_exp.md?raw";
import nsDoc_bitburner_multipliers_dexterity_md from "../../markdown/bitburner.multipliers.dexterity.md?raw";
import nsDoc_bitburner_multipliers_dnet_money_md from "../../markdown/bitburner.multipliers.dnet_money.md?raw";
import nsDoc_bitburner_multipliers_faction_rep_md from "../../markdown/bitburner.multipliers.faction_rep.md?raw";
import nsDoc_bitburner_multipliers_hacking_chance_md from "../../markdown/bitburner.multipliers.hacking_chance.md?raw";
import nsDoc_bitburner_multipliers_hacking_exp_md from "../../markdown/bitburner.multipliers.hacking_exp.md?raw";
@@ -2545,6 +2546,7 @@ AllPages["nsDoc/bitburner.multipliers.defense_exp.md"] = nsDoc_bitburner_multipl
AllPages["nsDoc/bitburner.multipliers.defense.md"] = nsDoc_bitburner_multipliers_defense_md;
AllPages["nsDoc/bitburner.multipliers.dexterity_exp.md"] = nsDoc_bitburner_multipliers_dexterity_exp_md;
AllPages["nsDoc/bitburner.multipliers.dexterity.md"] = nsDoc_bitburner_multipliers_dexterity_md;
AllPages["nsDoc/bitburner.multipliers.dnet_money.md"] = nsDoc_bitburner_multipliers_dnet_money_md;
AllPages["nsDoc/bitburner.multipliers.faction_rep.md"] = nsDoc_bitburner_multipliers_faction_rep_md;
AllPages["nsDoc/bitburner.multipliers.hacking_chance.md"] = nsDoc_bitburner_multipliers_hacking_chance_md;
AllPages["nsDoc/bitburner.multipliers.hacking_exp.md"] = nsDoc_bitburner_multipliers_hacking_exp_md;

View File

@@ -38,7 +38,11 @@ export function NetscriptCodingContract(): InternalAPI<ICodingContract> {
const resultOfCheckingSolution = contract.isSolution(answer);
switch (resultOfCheckingSolution.result) {
case CodingContractResult.Success: {
const reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty());
const reward = Player.gainCodingContractReward(
contract.reward,
contract.getDifficulty(),
contract.rewardScaling,
);
helpers.log(ctx, () => `Successfully completed Coding Contract '${contract.fn}'. Reward: ${reward}`);
server.removeContract(contract.fn);
return reward;

View File

@@ -426,7 +426,7 @@ export function NetscriptDarknet(): InternalAPI<DarknetAPI> {
const server = serverCheck.server;
const networkDelay =
calculateAuthenticationTime(server, Player, ctx.workerScript.scriptRef.threads, "", true) * 4;
calculateAuthenticationTime(server, Player, ctx.workerScript.scriptRef.threads, "", true) * 6;
const xp = formatNumber(calculatePasswordAttemptChaGain(server, ctx.workerScript.scriptRef.threads), 1);
logger(ctx)(`Captured some outgoing transmissions from ${server.hostname}. (Gained ${xp} cha xp)`);

View File

@@ -30,6 +30,7 @@ export const calculateEntropy = (stacks = 1): Multipliers => {
crime_money: Player.mults.crime_money * nerf,
crime_success: Player.mults.crime_success * nerf,
dnet_money: Player.mults.dnet_money * nerf,
hacknet_node_money: Player.mults.hacknet_node_money * nerf,
hacknet_node_purchase_cost: Player.mults.hacknet_node_purchase_cost / nerf,

View File

@@ -25,6 +25,7 @@ export interface Multipliers {
work_money: number;
crime_success: number;
crime_money: number;
dnet_money: number;
bladeburner_max_stamina: number;
bladeburner_stamina_gain: number;
bladeburner_analysis: number;
@@ -59,6 +60,7 @@ export const defaultMultipliers = (): Multipliers => {
work_money: 1,
crime_success: 1,
crime_money: 1,
dnet_money: 1,
bladeburner_max_stamina: 1,
bladeburner_stamina_gain: 1,
bladeburner_analysis: 1,
@@ -94,6 +96,7 @@ export const mergeMultipliers = (m0: Multipliers, m1: Multipliers): Multipliers
work_money: m0.work_money * m1.work_money,
crime_success: m0.crime_success * m1.crime_success,
crime_money: m0.crime_money * m1.crime_money,
dnet_money: m0.dnet_money * m1.dnet_money,
bladeburner_max_stamina: m0.bladeburner_max_stamina * m1.bladeburner_max_stamina,
bladeburner_stamina_gain: m0.bladeburner_stamina_gain * m1.bladeburner_stamina_gain,
bladeburner_analysis: m0.bladeburner_analysis * m1.bladeburner_analysis,
@@ -129,6 +132,7 @@ export const scaleMultipliers = (m0: Multipliers, v: number): Multipliers => {
work_money: (m0.work_money - 1) * v + 1,
crime_success: (m0.crime_success - 1) * v + 1,
crime_money: (m0.crime_money - 1) * v + 1,
dnet_money: (m0.dnet_money - 1) * v + 1,
bladeburner_max_stamina: (m0.bladeburner_max_stamina - 1) * v + 1,
bladeburner_stamina_gain: (m0.bladeburner_stamina_gain - 1) * v + 1,
bladeburner_analysis: (m0.bladeburner_analysis - 1) * v + 1,

View File

@@ -499,7 +499,8 @@ export function queueAugmentation(this: PlayerObject, name: AugmentationName): v
export function gainCodingContractReward(
this: PlayerObject,
reward: ICodingContractReward | null,
difficulty = 1,
difficulty: number,
rewardScaling: number,
): string {
if (!reward) {
return `No reward for this contract`;
@@ -509,20 +510,20 @@ export function gainCodingContractReward(
case CodingContractRewardType.FactionReputation: {
const factionsThatAllowHacking = Player.factions.filter((fac) => Factions[fac].getInfo().offerHackingWork);
if (factionsThatAllowHacking.length === 0) {
return this.gainCodingContractReward({ type: CodingContractRewardType.Money }, difficulty);
return this.gainCodingContractReward({ type: CodingContractRewardType.Money }, difficulty, rewardScaling);
}
const randomFaction = factionsThatAllowHacking[getRandomIntInclusive(0, factionsThatAllowHacking.length - 1)];
const repGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty;
const repGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty * rewardScaling;
Factions[randomFaction].playerReputation += repGain;
return `Gained ${repGain} faction reputation for ${randomFaction}`;
}
case CodingContractRewardType.FactionReputationAll: {
const factionsThatAllowHacking = Player.factions.filter((fac) => Factions[fac].getInfo().offerHackingWork);
if (factionsThatAllowHacking.length === 0) {
return this.gainCodingContractReward({ type: CodingContractRewardType.Money }, difficulty);
return this.gainCodingContractReward({ type: CodingContractRewardType.Money }, difficulty, rewardScaling);
}
const totalGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty;
const totalGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty * rewardScaling;
const gainPerFaction = Math.floor(totalGain / factionsThatAllowHacking.length);
for (const facName of factionsThatAllowHacking) {
Factions[facName].playerReputation += gainPerFaction;
@@ -542,15 +543,17 @@ export function gainCodingContractReward(
: CodingContractRewardType.FactionReputationAll,
},
difficulty,
rewardScaling,
);
}
const randomCompany = companies[getRandomIntInclusive(0, companies.length - 1)];
const repGain = CONSTANTS.CodingContractBaseCompanyRepGain * difficulty;
const repGain = CONSTANTS.CodingContractBaseCompanyRepGain * difficulty * rewardScaling;
Companies[randomCompany].playerReputation += repGain;
return `Gained ${repGain} company reputation for ${randomCompany}`;
}
case CodingContractRewardType.Money: {
const moneyGain = CONSTANTS.CodingContractBaseMoneyGain * difficulty * currentNodeMults.CodingContractMoney;
const moneyGain =
CONSTANTS.CodingContractBaseMoneyGain * difficulty * currentNodeMults.CodingContractMoney * rewardScaling;
this.gainMoney(moneyGain, "codingcontract");
return `Gained ${formatMoney(moneyGain)}`;
}

View File

@@ -15,7 +15,7 @@ function mult(favor: number): number {
export function getHackingWorkRepGain(p: IPerson, favor: number): number {
return (
((p.skills.hacking + p.skills.intelligence + getDarknetCharismaBonus(p, 0.15) / 3) / CONSTANTS.MaxSkillLevel) *
((p.skills.hacking + p.skills.intelligence / 3 + getDarknetCharismaBonus(p, 0.1)) / CONSTANTS.MaxSkillLevel) *
p.mults.faction_rep *
calculateIntelligenceBonus(p.skills.intelligence, 1) *
mult(favor) *
@@ -30,7 +30,8 @@ export function getFactionSecurityWorkRepGain(p: IPerson, favor: number): number
p.skills.defense +
p.skills.dexterity +
p.skills.agility +
(p.skills.hacking + p.skills.intelligence + getDarknetCharismaBonus(p, 0.3)) * calculateCurrentShareBonus())) /
getDarknetCharismaBonus(p, 0.3) +
(p.skills.hacking + p.skills.intelligence) * calculateCurrentShareBonus())) /
CONSTANTS.MaxSkillLevel /
4.5;
return t * p.mults.faction_rep * mult(favor) * calculateIntelligenceBonus(p.skills.intelligence, 1);

View File

@@ -178,6 +178,8 @@ interface Multipliers {
crime_money: number;
/** Multiplier to crime success rate */
crime_success: number;
/** Multiplier to amount of money gained from phishing and caches on darknet servers */
dnet_money: number;
/** Multiplier to amount of money gained from working */
work_money: number;
/** Multiplier to amount of money produced by Hacknet Nodes */
@@ -4497,13 +4499,16 @@ export interface Darknet {
* Sends a network request to try to authenticate on a darkweb server. The target server must be directly connected
* to the server that the script is running on. The speed of authentication scales with the number of threads used.
*
* If successful, grants the script a session, allowing it to exec() scripts on that server, or scp() files to it. (scp() *from* the server is always allowed.)
* If successful, grants the current script a session, allowing it to exec() scripts on that server, or scp() files to it. (scp() *from* the server is always allowed.)
*
* Note that the charisma level on a server is not a requirement for authentication, but authentication takes longer
* if the player's charisma is below the server's charisma level.
*
* Note that the session granted is only for the current script instance (by PID) - other running scripts will need to
* use connectToSession with the correct password to also get a session with the target server.
*
* @remarks
* RAM cost: 0.6 GB
* RAM cost: 0.4 GB
*
* @param host - Hostname/IP of the target server (connected to the current server) to try a password.
* @param password - Password to attempt to authenticate with.
@@ -4518,12 +4523,15 @@ export interface Darknet {
* Attempts to connect to a target darkweb server that you have previously authenticated on. Unlike `authenticate`,
* connectToSession can be used to get a session on servers at any distance.
*
* If successful, grants the script a session, allowing it to scp() files from that target. It also allows starting scripts
* If successful, grants the script a session, allowing it to scp() files to that target. It also allows starting scripts
* with exec() on that target, if the target is directly connected to the server that the script is running on,
* or has a backdoor or stasis link.
*
* If unsuccessful, more detail may be able to be gathered by using heartbleed() to look at the resulting logs on the server.
*
* Note that the session granted is only for the current script instance (by PID) - other running scripts will need to
* use connectToSession with the correct password to also get a session with the target server.
*
* @remarks
* RAM cost: 0.05 GB
*

View File

@@ -551,7 +551,11 @@ export class Terminal {
switch (promptResult.result) {
case CodingContractResult.Success:
if (contract.reward !== null) {
const reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty());
const reward = Player.gainCodingContractReward(
contract.reward,
contract.getDifficulty(),
contract.rewardScaling,
);
this.print(`Contract SUCCESS - ${reward}`);
}
server.removeContract(contract);

View File

@@ -534,6 +534,12 @@ export function CharacterStats(): React.ReactElement {
effValue: Player.mults.crime_money * currentNodeMults.CrimeMoney,
color: Settings.theme.money,
},
{
mult: "Darknet Money",
value: Player.mults.dnet_money,
effValue: Player.mults.dnet_money * currentNodeMults.DarknetMoneyMultiplier,
color: Settings.theme.money,
},
]}
color={Settings.theme.combat}
/>

View File

@@ -813,7 +813,7 @@ describe("Darknet server name generator", () => {
describe("Cache filename generator", () => {
test("Random prefix", () => {
for (let i = 0; i < 10000; ++i) {
const cacheFilename = generateCacheFilename();
const cacheFilename = generateCacheFilename(false);
if (!cacheFilename) {
throw new Error("Invalid cache filename");
}
@@ -821,7 +821,7 @@ describe("Cache filename generator", () => {
}
});
test("Cache file in labyrinth server", () => {
const cacheFilename = generateCacheFilename(LAB_CACHE_NAME);
const cacheFilename = generateCacheFilename(false, LAB_CACHE_NAME);
if (!cacheFilename) {
throw new Error("Invalid cache filename");
}

View File

@@ -774,7 +774,7 @@ describe("darkweb", () => {
test("openCache", () => {
const ns = getNsOnDarkWeb();
const darkweb = getDarknetServerOrThrow(SpecialServers.DarkWeb);
const result = addCacheToServer(darkweb, "test");
const result = addCacheToServer(darkweb, false, "test");
if (!result.success) {
throw new Error("Cannot add cache");
}

View File

@@ -517,6 +517,7 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
"defense_exp": 1,
"dexterity": 1,
"dexterity_exp": 1,
"dnet_money": 1,
"faction_rep": 1,
"hacking": 1,
"hacking_chance": 1,
@@ -601,6 +602,7 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
"defense_exp": 1,
"dexterity": 1.3,
"dexterity_exp": 1,
"dnet_money": 1,
"faction_rep": 1,
"hacking": 1,
"hacking_chance": 1,
@@ -676,6 +678,7 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
"defense_exp": 1,
"dexterity": 1,
"dexterity_exp": 1,
"dnet_money": 1,
"faction_rep": 1,
"hacking": 1,
"hacking_chance": 1,