diff --git a/assets/Steam/achievements/icons/darknet-depths.svg b/assets/Steam/achievements/icons/darknet-depths.svg
new file mode 100644
index 000000000..74d323a19
--- /dev/null
+++ b/assets/Steam/achievements/icons/darknet-depths.svg
@@ -0,0 +1,63 @@
+
+
+
+
diff --git a/dist/icons/achievements/darknet-depths.svg b/dist/icons/achievements/darknet-depths.svg
new file mode 100644
index 000000000..bd34261c4
--- /dev/null
+++ b/dist/icons/achievements/darknet-depths.svg
@@ -0,0 +1,63 @@
+
+
+
+
diff --git a/src/Achievements/AchievementData.json b/src/Achievements/AchievementData.json
index 096ce6aa2..46ec2e1b7 100644
--- a/src/Achievements/AchievementData.json
+++ b/src/Achievements/AchievementData.json
@@ -416,6 +416,11 @@
"Name": "Make your Own Network",
"Description": "Install a backdoor on 50 or more darknet servers at once."
},
+ "DARKNET_DEPTHS": {
+ "ID": "DARKNET_DEPTHS",
+ "Name": "Into the Depths",
+ "Description": "Install the augment from the deepest server."
+ },
"CHALLENGE_BN1": {
"ID": "CHALLENGE_BN1",
"Name": "BN1: Challenge",
@@ -474,7 +479,7 @@
"CHALLENGE_BN15": {
"ID": "CHALLENGE_BN15",
"Name": "BN15: Challenge",
- "Description": "Open the cache on the final lab in the darknet."
+ "Description": "Complete BN15 without ever calling dnet.heartbleed."
},
"BYPASS": {
"ID": "BYPASS",
diff --git a/src/Achievements/Achievements.ts b/src/Achievements/Achievements.ts
index 286659aaa..13f6ce3ff 100644
--- a/src/Achievements/Achievements.ts
+++ b/src/Achievements/Achievements.ts
@@ -38,6 +38,7 @@ import { Go } from "../Go/Go";
import { type AchievementId, type SFAchievementId, SFAchievementIds } from "./Types";
import { getAllMovableDarknetServers } from "../DarkNet/utils/darknetNetworkUtils";
+import { DarknetState } from "../DarkNet/models/DarknetState";
function assertAchievements(
achievements: typeof data.achievements,
@@ -591,6 +592,13 @@ export const achievements: Record = {
Condition: () => getAllMovableDarknetServers().filter((s) => s.backdoorInstalled).length >= 50,
NotInSteam: true,
},
+ DARKNET_DEPTHS: {
+ ...achievementData.DARKNET_DEPTHS,
+ Icon: "darknet-depths",
+ Visible: knowAboutBitverse,
+ Condition: () => Player.augmentations.some((a) => a.name === AugmentationName.TheSword),
+ NotInSteam: true,
+ },
CHALLENGE_BN1: {
...achievementData.CHALLENGE_BN1,
Icon: "BN1+",
@@ -685,7 +693,7 @@ export const achievements: Record = {
...achievementData.CHALLENGE_BN15,
Icon: "BN15+",
Visible: knowAboutBitverse,
- Condition: () => Player.augmentations.some((a) => a.name === AugmentationName.TheSword),
+ Condition: () => Player.bitNodeN === 15 && isBitNodeFinished() && !DarknetState.hasUsedHeartbleed,
NotInSteam: true,
},
BYPASS: {
diff --git a/src/DarkNet/effects/SaveLoad.ts b/src/DarkNet/effects/SaveLoad.ts
index 7a8dc78a8..3fadfc31a 100644
--- a/src/DarkNet/effects/SaveLoad.ts
+++ b/src/DarkNet/effects/SaveLoad.ts
@@ -3,11 +3,13 @@ import { assertObject } from "../../utils/TypeAssertion";
export type DarknetSaveFormat = {
storedCycles: number;
+ hasUsedHeartbleed: boolean;
};
export function getDarkNetSave(): DarknetSaveFormat {
return {
storedCycles: Math.floor(DarknetState.storedCycles),
+ hasUsedHeartbleed: DarknetState.hasUsedHeartbleed,
};
}
@@ -18,11 +20,12 @@ export function loadDarkNet(saveString: unknown): void {
try {
const parsedData: unknown = JSON.parse(saveString);
assertObject(parsedData);
- const { storedCycles } = parsedData;
+ const { storedCycles, hasUsedHeartbleed } = parsedData;
if (typeof storedCycles !== "number" || !Number.isFinite(storedCycles)) {
throw new Error(`Invalid storedCycles: ${storedCycles}`);
}
DarknetState.storedCycles = storedCycles < 0 ? 0 : storedCycles;
+ DarknetState.hasUsedHeartbleed = Boolean(hasUsedHeartbleed);
} catch (error) {
console.error(error);
console.error("Invalid DarkNet data:", saveString);
diff --git a/src/DarkNet/models/DarknetState.ts b/src/DarkNet/models/DarknetState.ts
index 9ded8e374..8b6f2e301 100644
--- a/src/DarkNet/models/DarknetState.ts
+++ b/src/DarkNet/models/DarknetState.ts
@@ -28,6 +28,7 @@ export const DarknetState = {
nextMutation: Promise.resolve(),
nextMutationResolver: null as (() => void) | null,
storedCycles: 0,
+ hasUsedHeartbleed: false,
cyclesSinceLastMutation: 0,
Network: new Array(MAX_NET_DEPTH).fill(null).map(() => new Array(NET_WIDTH).fill(null)),
diff --git a/src/DarkNet/ui/NetworkDisplayWrapper.tsx b/src/DarkNet/ui/NetworkDisplayWrapper.tsx
index 31dd164e9..7d0f0625f 100644
--- a/src/DarkNet/ui/NetworkDisplayWrapper.tsx
+++ b/src/DarkNet/ui/NetworkDisplayWrapper.tsx
@@ -15,7 +15,7 @@ import { useRerender } from "../../ui/React/hooks";
import { DarknetEvents, DarknetState } from "../models/DarknetState";
import { SpecialServers } from "../../Server/data/SpecialServers";
import { drawOnCanvas, getPixelPosition } from "./networkCanvas";
-import { dnetStyles } from "./dnetStyles";
+import { dnetStyles, DWServerLogStyles } from "./dnetStyles";
import { getLabyrinthDetails, isLabyrinthServer } from "../effects/labyrinth";
import { DarknetServer } from "../../Server/DarknetServer";
import { getAllDarknetServers } from "../utils/darknetNetworkUtils";
@@ -358,7 +358,12 @@ export function NetworkDisplayWrapper(): React.ReactElement {
Darknet Docs
diff --git a/src/NetscriptFunctions/Darknet.ts b/src/NetscriptFunctions/Darknet.ts
index b0f618f90..97ca603b7 100644
--- a/src/NetscriptFunctions/Darknet.ts
+++ b/src/NetscriptFunctions/Darknet.ts
@@ -247,6 +247,7 @@ export function NetscriptDarknet(): InternalAPI {
logger(ctx)(
`Attempting to extract data from ${server.hostname}... (Est: ${formatNumber(networkDelay / 1000, 1)}s)`,
);
+ DarknetState.hasUsedHeartbleed = true;
if (Player.skills.charisma < server.requiredCharismaSkill) {
logger(ctx)(
@@ -263,6 +264,7 @@ export function NetscriptDarknet(): InternalAPI {
return helpers.netscriptDelay(ctx, networkDelay).then(() => {
const xpGained = Player.mults.charisma_exp * 50 * ((500 + Player.skills.charisma) / 500);
Player.gainCharismaExp(xpGained);
+
const onlineConnectionCheck = getFailureResult(ctx, targetHost, { requireDirectConnection: true });
if (!onlineConnectionCheck.success) {
return {