mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
287 lines
12 KiB
TypeScript
287 lines
12 KiB
TypeScript
import { Player } from "@player";
|
|
import type { DarknetServerData, Person as IPerson } from "@nsdefs";
|
|
import { AugmentationName, CompletedProgramName, LiteratureName } from "@enums";
|
|
import {
|
|
commonPasswordDictionary,
|
|
notebookFileNames,
|
|
packetSniffPhrases,
|
|
passwordFileNames,
|
|
} from "../models/dictionaryData";
|
|
import { hintLiterature } from "../models/hintNotes";
|
|
import { resolveTextFilePath, type TextFilePath } from "../../Paths/TextFilePath";
|
|
import { moveDarknetServer } from "../controllers/NetworkMovement";
|
|
import { calculateIntelligenceBonus } from "../../PersonObjects/formulas/intelligence";
|
|
import { addSessionToServer, DarknetState, hasDarknetBonusTime } from "../models/DarknetState";
|
|
import { DarknetServer } from "../../Server/DarknetServer";
|
|
import { GenericResponseMessage, ModelIds, NET_WIDTH, ResponseCodeEnum } from "../Enums";
|
|
import { addCacheToServer } from "./cacheFiles";
|
|
import { populateDarknet } from "../controllers/NetworkGenerator";
|
|
import { getDarknetServer } from "../utils/darknetServerUtils";
|
|
import {
|
|
getAllMovableDarknetServers,
|
|
getBackdooredDarknetServers,
|
|
getNearbyNonEmptyPasswordServer,
|
|
getStasisLinkServers,
|
|
} from "../utils/darknetNetworkUtils";
|
|
import { getSharedChars, getTwoCharsInPassword } from "../utils/darknetAuthUtils";
|
|
import { getTorRouter } from "../../Server/ServerHelpers";
|
|
import { DarknetConstants } from "../Constants";
|
|
import { GetServer } from "../../Server/AllServers";
|
|
import { isLabyrinthServer } from "./labyrinth";
|
|
import { NetscriptContext } from "../../Netscript/APIWrapper";
|
|
import { helpers } from "../../Netscript/NetscriptHelpers";
|
|
|
|
export const handleSuccessfulAuth = (server: DarknetServer, threads: number, pid: number) => {
|
|
Player.gainCharismaExp(calculatePasswordAttemptChaGain(server, threads, true));
|
|
addSessionToServer(server, pid);
|
|
|
|
if (server.hasAdminRights) return;
|
|
|
|
server.hasAdminRights = true;
|
|
addClue(server);
|
|
|
|
const chance = 0.1 * 1.05 ** server?.difficulty;
|
|
if (Math.random() < chance && !isLabyrinthServer(server.hostname)) {
|
|
addCacheToServer(server, false);
|
|
}
|
|
};
|
|
|
|
export const handleFailedAuth = (server: DarknetServer, threads: number) => {
|
|
Player.gainCharismaExp(calculatePasswordAttemptChaGain(server, threads, false));
|
|
};
|
|
|
|
/**
|
|
* Returns the time it takes to authenticate on a server in milliseconds
|
|
* @param darknetServerData - the target server to attempt a password on
|
|
* @param person - the player's character
|
|
* @param attemptedPassword - the password being attempted
|
|
* @param threads - the number of threads used for the password attempt (which speeds up the process)
|
|
* @param linear - if true, the time scaling is linear with the number of threads instead of having diminishing returns
|
|
*/
|
|
export const calculateAuthenticationTime = (
|
|
darknetServerData: DarknetServerData,
|
|
person: IPerson = Player,
|
|
threads = 1,
|
|
attemptedPassword = "",
|
|
linear = false,
|
|
) => {
|
|
const chaRequired = darknetServerData.requiredCharismaSkill;
|
|
const difficulty = darknetServerData.difficulty;
|
|
|
|
const baseDiff = (difficulty + 1) * 100;
|
|
const diffFactor = 5;
|
|
const baseTime = 850;
|
|
|
|
const threadsFactor = 1 / (linear ? threads : 1 + 0.2 * (threads - 1));
|
|
const skillFactor = (diffFactor * chaRequired + baseDiff) / (person.skills.charisma + 150);
|
|
const backdoorFactor = getBackdoorAuthTimeDebuff();
|
|
const applyUnderleveledFactor = person.skills.charisma <= chaRequired && darknetServerData.depth > 1;
|
|
const underleveledFactor = applyUnderleveledFactor ? 1.5 + (chaRequired + 50) / (person.skills.charisma + 50) : 1;
|
|
const hasBootsFactor = Player.hasAugmentation(AugmentationName.TheBoots) ? 0.8 : 1;
|
|
const hasSf15_2Factor = Player.activeSourceFileLvl(15) > 2 ? 0.8 : 1;
|
|
|
|
const time =
|
|
baseTime * skillFactor * backdoorFactor * underleveledFactor * hasBootsFactor * hasSf15_2Factor * threadsFactor;
|
|
|
|
// We need to call GetServer and check if it's a dnet server later because this function can be called by formulas
|
|
// APIs (darknetServerData.hostname may be an invalid hostname).
|
|
const server = GetServer(darknetServerData.hostname);
|
|
const password = server instanceof DarknetServer ? server.password : "";
|
|
// Add extra time for timing attack server, per correct character
|
|
const sharedChars =
|
|
darknetServerData.modelId === ModelIds.TimingAttack ? getSharedChars(password, attemptedPassword) : 0;
|
|
const sharedCharsExtraTime = sharedChars * 50 * threadsFactor;
|
|
|
|
return time * calculateIntelligenceBonus(person.skills.intelligence, 0.25) + sharedCharsExtraTime;
|
|
};
|
|
|
|
export const getBackdoorAuthTimeDebuff = () => {
|
|
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);
|
|
|
|
return 1.07 ** backdoorSurplus;
|
|
};
|
|
|
|
/**
|
|
* Returns a small multiplier based on charisma.
|
|
* With scalar at 1 it gives ~1.2 at 2000 charisma and ~1.6 at 10000 charisma. Caps at 2.1 at infinite cha
|
|
*/
|
|
export const getMultiplierFromCharisma = (scalar = 1) => {
|
|
const charisma = Player.skills.charisma;
|
|
const growthRate = 0.0002; // Adjust this value to control the growth rate
|
|
return (
|
|
1 + (0.5 * (1 - Math.exp(-growthRate * charisma)) + 0.6 * (1 - Math.exp(-growthRate * 0.2 * charisma)) * scalar)
|
|
);
|
|
};
|
|
|
|
export const calculatePasswordAttemptChaGain = (server: DarknetServerData, threads: number = 1, success = false) => {
|
|
const baseXpGain = 3;
|
|
const difficultyBase = 1.1;
|
|
const xpGain = baseXpGain + difficultyBase ** server.difficulty;
|
|
const alreadyHackedMult = server.hasAdminRights ? 0.2 : 1;
|
|
const successMult = success && !server.hasAdminRights ? 10 : 1;
|
|
const bonusTimeMult = hasDarknetBonusTime() ? 1.5 : 1;
|
|
return xpGain * alreadyHackedMult * successMult * bonusTimeMult * threads * Player.mults.charisma_exp;
|
|
};
|
|
|
|
// TODO: balance password clue spawn rate
|
|
export const addClue = (server: DarknetServer) => {
|
|
// Basic mechanics hints
|
|
if ((Math.random() < 0.7 && server.difficulty <= 3) || Math.random() < 0.1) {
|
|
const hint: LiteratureName = hintLiterature[Math.floor(Math.random() * hintLiterature.length)];
|
|
if (hint) {
|
|
server.messages.push(hint);
|
|
}
|
|
}
|
|
|
|
// some entries from the common password dictionary
|
|
if (Math.random() < 0.1) {
|
|
const length = 15;
|
|
const hintFileName = getClueFileName(passwordFileNames);
|
|
const start = Math.floor(Math.random() * (commonPasswordDictionary.length - length));
|
|
const commonPasswords = commonPasswordDictionary.slice(start, start + length).join(", ");
|
|
server.writeToTextFile(hintFileName, `Some common passwords include ${commonPasswords}`);
|
|
return;
|
|
}
|
|
|
|
// connected neighboring server's password (does not include server name)
|
|
if (Math.random() < 0.1) {
|
|
const passwordHintName = getClueFileName(passwordFileNames);
|
|
const neighboringServerName = server.serversOnNetwork.find((s) => {
|
|
const server = getDarknetServer(s);
|
|
return server && !server.hasAdminRights && server.password;
|
|
});
|
|
const neighboringServer = neighboringServerName ? getDarknetServer(neighboringServerName) : null;
|
|
if (neighboringServer) {
|
|
server.writeToTextFile(passwordHintName, `Remember this password: ${neighboringServer.password}`);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// non-connected nearby server's password (includes server name)
|
|
if (Math.random() < 0.1) {
|
|
const hintFileName = getClueFileName(passwordFileNames);
|
|
const targetServer = getNearbyNonEmptyPasswordServer(server, true);
|
|
if (targetServer) {
|
|
const contents = `Server: ${targetServer.hostname} Password: "${targetServer.password}"`;
|
|
server.writeToTextFile(hintFileName, contents);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (Math.random() < 0.4) {
|
|
const hintFileName = getClueFileName(notebookFileNames);
|
|
const loreNote = packetSniffPhrases[Math.floor(Math.random() * packetSniffPhrases.length)];
|
|
server.writeToTextFile(hintFileName, loreNote);
|
|
return;
|
|
}
|
|
|
|
if (Math.random() < 0.7) {
|
|
const hintFileName = getClueFileName(passwordFileNames);
|
|
const targetServer = getNearbyNonEmptyPasswordServer(server);
|
|
if (targetServer) {
|
|
const [containedChar1, containedChar2] = getTwoCharsInPassword(targetServer.password);
|
|
const hint = `The password for ${targetServer.hostname} contains ${containedChar1} and ${containedChar2}`;
|
|
server.writeToTextFile(hintFileName, hint);
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
export const getClueFileName = (fileNameList: readonly string[]): TextFilePath => {
|
|
const filePath = resolveTextFilePath(
|
|
fileNameList[Math.floor(Math.random() * fileNameList.length)] + DarknetConstants.DataFileSuffix,
|
|
);
|
|
if (!filePath) {
|
|
throw new Error(`Invalid clue file name. fileNameList: ${fileNameList}`);
|
|
}
|
|
return filePath;
|
|
};
|
|
|
|
export const getDarknetVolatilityMult = (symbol: string) => {
|
|
const charges = DarknetState.stockPromotions[symbol] ?? 0;
|
|
const growthRate = 0.001;
|
|
return 1 + (1 - Math.exp(-growthRate * charges) + 2 * (1 - Math.exp(-growthRate * 0.15 * charges)));
|
|
};
|
|
|
|
export const scaleDarknetVolatilityIncreases = (scalar: number) => {
|
|
for (const symbol in DarknetState.stockPromotions) {
|
|
if (DarknetState.stockPromotions[symbol] > 0) {
|
|
DarknetState.stockPromotions[symbol] *= scalar;
|
|
}
|
|
}
|
|
};
|
|
|
|
export const getStasisLinkLimit = (): number => {
|
|
const brokenWingLimitIncrease = Player.hasAugmentation(AugmentationName.TheBrokenWings) ? 1 : 0;
|
|
const hammerLimitIncrease = Player.hasAugmentation(AugmentationName.TheHammer) ? 1 : 0;
|
|
const staffLimitIncrease = Player.hasAugmentation(AugmentationName.TheStaff) ? 1 : 0;
|
|
return 1 + brokenWingLimitIncrease + hammerLimitIncrease + staffLimitIncrease;
|
|
};
|
|
|
|
export const getSetStasisLinkDuration = (): number => {
|
|
return (1000 / (Player.skills.charisma + 1000)) * 30_000;
|
|
};
|
|
|
|
export const setStasisLink = (ctx: NetscriptContext, server: DarknetServer, shouldLink: boolean) => {
|
|
const stasisLinkCount = getStasisLinkServers().length;
|
|
const stasisLinkLimit = getStasisLinkLimit();
|
|
if (shouldLink && stasisLinkCount >= stasisLinkLimit) {
|
|
helpers.log(ctx, () => `Stasis link limit reached. (${stasisLinkCount}/${stasisLinkLimit})`);
|
|
return {
|
|
success: false,
|
|
code: ResponseCodeEnum.StasisLinkLimitReached,
|
|
message: GenericResponseMessage.StasisLinkLimitReached,
|
|
};
|
|
}
|
|
|
|
server.hasStasisLink = shouldLink;
|
|
server.backdoorInstalled = shouldLink;
|
|
const message = `Stasis link ${shouldLink ? "applied to" : "removed from"} server ${server.hostname}.`;
|
|
helpers.log(ctx, () => `${message}. (${stasisLinkCount}/${stasisLinkLimit} links in use)`);
|
|
return {
|
|
success: true,
|
|
code: ResponseCodeEnum.Success,
|
|
message: GenericResponseMessage.Success,
|
|
};
|
|
};
|
|
|
|
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 * 5 * threads * server.difficulty;
|
|
Player.gainCharismaExp(xpGained);
|
|
const currentCharge = DarknetState.migrationInductionServers.get(server.hostname) ?? 0;
|
|
const newCharge = Math.min(currentCharge + chargeIncrease, 1);
|
|
DarknetState.migrationInductionServers.set(server.hostname, newCharge);
|
|
const result = {
|
|
chargeIncrease,
|
|
newCharge: newCharge,
|
|
xpGained: xpGained,
|
|
};
|
|
if (newCharge >= 1) {
|
|
moveDarknetServer(server, -2, 4);
|
|
DarknetState.migrationInductionServers.set(server.hostname, 0);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
export const getDarkscapeNavigator = () => {
|
|
if (!Player.hasTorRouter()) {
|
|
getTorRouter();
|
|
}
|
|
const existingPrograms = Player.getHomeComputer().programs;
|
|
if (!existingPrograms.includes(CompletedProgramName.darkscape)) {
|
|
Player.getHomeComputer().pushProgram(CompletedProgramName.darkscape);
|
|
}
|
|
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;
|