From 844d5186843c37365d4f4699eb6bb2f1d3fa78ff Mon Sep 17 00:00:00 2001 From: Martin Fournier Date: Thu, 6 Jan 2022 07:04:03 -0500 Subject: [PATCH 1/3] Add achievements to base game - Add a script to generate achievement data from Steamworks API - Add achievements page with a link in sidebar - Calculate achievements (1/min) with an engine counter - Store achievements with a timestamp on unlocked in the PlayerObject - Add a script to generate monochrome icons from Steam icons - Add toast when unlocking an achievement --- assets/Steam/achievements/pack-for-web.sh | 15 + package.sh | 1 + src/Achievements/AchievementData.json | 486 +++++++++++ src/Achievements/AchievementEntry.tsx | 60 ++ src/Achievements/AchievementIcon.tsx | 35 + src/Achievements/AchievementList.tsx | 121 +++ src/Achievements/Achievements.ts | 768 ++++++++++++++++++ src/Achievements/AchievementsRoot.tsx | 30 + src/Achievements/README.md | 9 + src/Electron.tsx | 415 +--------- src/IEngine.ts | 1 + src/PersonObjects/IPlayer.ts | 3 + src/PersonObjects/Player/PlayerObject.ts | 5 + .../Player/PlayerObjectGeneralMethods.tsx | 10 + src/Sidebar/ui/SidebarRoot.tsx | 20 + src/ThirdParty/colorUtils.ts | 315 +++++++ src/engine.tsx | 8 + src/ui/GameRoot.tsx | 11 +- src/ui/Router.ts | 2 + tools/README.md | 10 + tools/fetch-steam-achievements-data.js | 69 ++ tsconfig.json | 3 +- webpack.config.js | 2 +- 23 files changed, 1983 insertions(+), 416 deletions(-) create mode 100644 assets/Steam/achievements/pack-for-web.sh create mode 100644 src/Achievements/AchievementData.json create mode 100644 src/Achievements/AchievementEntry.tsx create mode 100644 src/Achievements/AchievementIcon.tsx create mode 100644 src/Achievements/AchievementList.tsx create mode 100644 src/Achievements/Achievements.ts create mode 100644 src/Achievements/AchievementsRoot.tsx create mode 100644 src/Achievements/README.md create mode 100644 src/ThirdParty/colorUtils.ts create mode 100644 tools/fetch-steam-achievements-data.js diff --git a/assets/Steam/achievements/pack-for-web.sh b/assets/Steam/achievements/pack-for-web.sh new file mode 100644 index 000000000..f00431ff2 --- /dev/null +++ b/assets/Steam/achievements/pack-for-web.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +BASEDIR=$(dirname "$0") +ROOTDIR=$BASEDIR/../../.. +echo $ROOTDIR +rm -rf $ROOTDIR/dist/icons/achievements +mkdir -p $ROOTDIR/dist/icons +cp -r $BASEDIR/real $ROOTDIR/dist/icons/achievements +for i in $ROOTDIR/dist/icons/achievements/*.svg; do + echo $i + # Make background transparent and replace green with black + # The icons will be recolored by css filters matching the player's theme + sed -i "s/fill:#000000;/fill-opacity: 0%;/g" "$i" + sed -i "s/fill:#00ff00;/fill:#000000;/g" "$i" +done diff --git a/package.sh b/package.sh index 94dbb9306..866be1700 100755 --- a/package.sh +++ b/package.sh @@ -7,6 +7,7 @@ mkdir -p .package/node_modules || true cp index.html .package cp -r electron/* .package cp -r dist/ext .package/dist +cp -r dist/icons .package/dist # The css files cp dist/vendor.css .package/dist diff --git a/src/Achievements/AchievementData.json b/src/Achievements/AchievementData.json new file mode 100644 index 000000000..7228ea889 --- /dev/null +++ b/src/Achievements/AchievementData.json @@ -0,0 +1,486 @@ +{ + "note": "***** Generated from a script, overwritten by steam achievements data *****", + "fetchedOn": 1641517584274, + "achievements": { + "CYBERSEC": { + "ID": "CYBERSEC", + "Name": "CyberSec", + "Description": "Join CyberSec." + }, + "NITESEC": { + "ID": "NITESEC", + "Name": "avmnite-02h", + "Description": "Join NiteSec." + }, + "THE_BLACK_HAND": { + "ID": "THE_BLACK_HAND", + "Name": "I.I.I.I", + "Description": "Join The Black Hand." + }, + "BITRUNNERS": { + "ID": "BITRUNNERS", + "Name": "run4theh111z", + "Description": "Join the BitRunners." + }, + "DAEDALUS": { + "ID": "DAEDALUS", + "Name": "fl1ght.exe", + "Description": "Join Daedalus." + }, + "THE_COVENANT": { + "ID": "THE_COVENANT", + "Name": "The Covenant", + "Description": "Join The Covenant." + }, + "ILLUMINATI": { + "ID": "ILLUMINATI", + "Name": "Illuminati", + "Description": "Join the Illuminati." + }, + "BRUTESSH.EXE": { + "ID": "BRUTESSH.EXE", + "Name": "BruteSSH.exe", + "Description": "Acquire BruteSSH.exe" + }, + "FTPCRACK.EXE": { + "ID": "FTPCRACK.EXE", + "Name": "FTPCrack.exe", + "Description": "Acquire FTPCrack.exe" + }, + "RELAYSMTP.EXE": { + "ID": "RELAYSMTP.EXE", + "Name": "relaySMTP.exe", + "Description": "Acquire relaySMTP.exe" + }, + "HTTPWORM.EXE": { + "ID": "HTTPWORM.EXE", + "Name": "HTTPWorm.exe", + "Description": "Acquire HTTPWorm.exe" + }, + "SQLINJECT.EXE": { + "ID": "SQLINJECT.EXE", + "Name": "SQLInject.exe", + "Description": "Acquire SQLInject.exe" + }, + "FORMULAS.EXE": { + "ID": "FORMULAS.EXE", + "Name": "Formulas.exe", + "Description": "Acquire Formulas.exe" + }, + "SF1.1": { + "ID": "SF1.1", + "Name": "Source Genesis", + "Description": "Acquire SF1.1" + }, + "SF2.1": { + "ID": "SF2.1", + "Name": "Rise of the Underworld", + "Description": "Acquire SF2.1" + }, + "SF3.1": { + "ID": "SF3.1", + "Name": "Corporatocracy", + "Description": "Acquire SF3.1" + }, + "SF4.1": { + "ID": "SF4.1", + "Name": "The Singularity", + "Description": "Acquire SF4.1" + }, + "SF5.1": { + "ID": "SF5.1", + "Name": "Artificial Intelligence", + "Description": "Acquire SF5.1" + }, + "SF6.1": { + "ID": "SF6.1", + "Name": "Bladeburners", + "Description": "Acquire SF6.1" + }, + "SF7.1": { + "ID": "SF7.1", + "Name": "Bladeburners 2079", + "Description": "Acquire SF7.1" + }, + "SF8.1": { + "ID": "SF8.1", + "Name": "Ghost of Wall Street", + "Description": "Acquire SF8.1" + }, + "SF9.1": { + "ID": "SF9.1", + "Name": "Hacktocracy", + "Description": "Acquire SF9.1" + }, + "SF10.1": { + "ID": "SF10.1", + "Name": "Digital Carbon", + "Description": "Acquire SF10.1" + }, + "SF11.1": { + "ID": "SF11.1", + "Name": "The Big Crash", + "Description": "Acquire SF11.1" + }, + "SF12.1": { + "ID": "SF12.1", + "Name": "The Recursion", + "Description": "Acquire SF12.1" + }, + "MONEY_1Q": { + "ID": "MONEY_1Q", + "Name": "Here comes the money!", + "Description": "Have $1Q on your home computer." + }, + "MONEY_M1B": { + "ID": "MONEY_M1B", + "Name": "Massive debt", + "Description": "Be $1b in debt." + }, + "INSTALL_1": { + "ID": "INSTALL_1", + "Name": "I never asked for this.", + "Description": "Install your first augmentation." + }, + "INSTALL_100": { + "ID": "INSTALL_100", + "Name": "I asked for this.", + "Description": "Have 100 augmentation installed at once." + }, + "QUEUE_40": { + "ID": "QUEUE_40", + "Name": "It's time to install", + "Description": "Have 40 augmentation queued at once." + }, + "HACKING_100000": { + "ID": "HACKING_100000", + "Name": "Power Overwhelming", + "Description": "Achieve 100 000 hacking skill." + }, + "COMBAT_3000": { + "ID": "COMBAT_3000", + "Name": "One punch man", + "Description": "Achieve 3000 in all combat stats." + }, + "NEUROFLUX_255": { + "ID": "NEUROFLUX_255", + "Name": "Neuroflux is love, Neuroflux is live", + "Description": "Install Neuroflux Governor level 255" + }, + "NS2": { + "ID": "NS2", + "Name": "Maximum speed!", + "Description": "Write an ns2 script." + }, + "FROZE": { + "ID": "FROZE", + "Name": "while(true);", + "Description": "Restart the game using the reload & kill all option because you froze it with an infinite loop." + }, + "RUNNING_SCRIPTS_1000": { + "ID": "RUNNING_SCRIPTS_1000", + "Name": "Need more real life ram", + "Description": "Run 1000 scripts simultaneously." + }, + "DRAIN_SERVER": { + "ID": "DRAIN_SERVER", + "Name": "Big trouble", + "Description": "Drain a server of all its money." + }, + "MAX_RAM": { + "ID": "MAX_RAM", + "Name": "Download more ram", + "Description": "Maximize your home computer ram." + }, + "MAX_CORES": { + "ID": "MAX_CORES", + "Name": "Download more cores?", + "Description": "Maximize your home computer cores." + }, + "SCRIPTS_30": { + "ID": "SCRIPTS_30", + "Name": "Thank you folders!", + "Description": "Have 30 scripts on your home computer." + }, + "KARMA_1000000": { + "ID": "KARMA_1000000", + "Name": "Wretched hive of scum and vilany", + "Description": "Reach -1m karma." + }, + "STOCK_1q": { + "ID": "STOCK_1q", + "Name": "Wolf of wall stree.", + "Description": "Make 1q on the stock market." + }, + "DISCOUNT": { + "ID": "DISCOUNT", + "Name": "Discount!", + "Description": "Get a discount at Powerhouse Gym by backdooring their server." + }, + "SCRIPT_32GB": { + "ID": "SCRIPT_32GB", + "Name": "You'll need upgrade for this one.", + "Description": "Write a script that costs 32GB per thread." + }, + "FIRST_HACKNET_NODE": { + "ID": "FIRST_HACKNET_NODE", + "Name": "Free money!", + "Description": "Purchase your first hacknet node." + }, + "30_HACKNET_NODE": { + "ID": "30_HACKNET_NODE", + "Name": "Big network", + "Description": "Have 30 hacknet nodes." + }, + "MAX_HACKNET_NODE": { + "ID": "MAX_HACKNET_NODE", + "Name": "That's the limit", + "Description": "Maximize a hacknet node." + }, + "HACKNET_NODE_10M": { + "ID": "HACKNET_NODE_10M", + "Name": "The original hacker", + "Description": "Make 10m from hacknet nodes." + }, + "REPUTATION_10M": { + "ID": "REPUTATION_10M", + "Name": "Well liked", + "Description": "Reach 10m reputation with a faction." + }, + "DONATION": { + "ID": "DONATION", + "Name": "Donate!", + "Description": "Unlock donations with a faction." + }, + "TRAVEL": { + "ID": "TRAVEL", + "Name": "World explorer", + "Description": "Travel anywhere." + }, + "WORKOUT": { + "ID": "WORKOUT", + "Name": "Gains!", + "Description": "Workout at a gym." + }, + "TOR": { + "ID": "TOR", + "Name": "The Onion Network", + "Description": "Purchase the TOR router." + }, + "HOSPITALIZED": { + "ID": "HOSPITALIZED", + "Name": "Ouch!", + "Description": "Go to the hospital." + }, + "GANG": { + "ID": "GANG", + "Name": "Gangster", + "Description": "Form a gang." + }, + "FULL_GANG": { + "ID": "FULL_GANG", + "Name": "Don", + "Description": "Recruit all gang members." + }, + "GANG_TERRITORY": { + "ID": "GANG_TERRITORY", + "Name": "Stay out of my territory", + "Description": "Have 100% of the territory." + }, + "GANG_MEMBER_POWER": { + "ID": "GANG_MEMBER_POWER", + "Name": "One punch guy", + "Description": "Have a gang member with 10 000 in 1 skill." + }, + "CORPORATION": { + "ID": "CORPORATION", + "Name": "A small 150b loan.", + "Description": "Create a corporation." + }, + "CORPORATION_BRIBE": { + "ID": "CORPORATION_BRIBE", + "Name": "Lobbying is great!", + "Description": "Lower your taxes through lobbying." + }, + "CORPORATION_PROD_1000": { + "ID": "CORPORATION_PROD_1000", + "Name": "Streamlined manufacturing", + "Description": "Have a division with a production multiplier of 1000." + }, + "CORPORATION_EMPLOYEE_3000": { + "ID": "CORPORATION_EMPLOYEE_3000", + "Name": "Small town", + "Description": "Have a division with 3000 employee." + }, + "CORPORATION_REAL_ESTATE": { + "ID": "CORPORATION_REAL_ESTATE", + "Name": "Own the land", + "Description": "Expand to the Real Estate division." + }, + "INTELLIGENCE_255": { + "ID": "INTELLIGENCE_255", + "Name": "Smart!", + "Description": "Reach intelligence 255" + }, + "BLADEBURNER_DIVISION": { + "ID": "BLADEBURNER_DIVISION", + "Name": "Bladeburners", + "Description": "Join the Bladeburner division." + }, + "BLADEBURNER_OVERCLOCK": { + "ID": "BLADEBURNER_OVERCLOCK", + "Name": "Overclock!", + "Description": "Reach maximum level of Overclock" + }, + "BLADEBURNER_UNSPENT_100000": { + "ID": "BLADEBURNER_UNSPENT_100000", + "Name": "You should really spent those.", + "Description": "Have 100 000 unspent bladeburner skill points." + }, + "4S": { + "ID": "4S", + "Name": "4S", + "Description": "Purchase the 4S market data." + }, + "FIRST_HACKNET_SERVER": { + "ID": "FIRST_HACKNET_SERVER", + "Name": "The improved hacker.", + "Description": "Purchase your first hacknet server." + }, + "ALL_HACKNET_SERVER": { + "ID": "ALL_HACKNET_SERVER", + "Name": "Full network", + "Description": "Buy all hacknet servers." + }, + "MAX_HACKNET_SERVER": { + "ID": "MAX_HACKNET_SERVER", + "Name": "That's the new limit.", + "Description": "Maximize a hacknet server." + }, + "HACKNET_SERVER_1B": { + "ID": "HACKNET_SERVER_1B", + "Name": "Not passive anymore", + "Description": "Make $1b with hacknet servers." + }, + "MAX_CACHE": { + "ID": "MAX_CACHE", + "Name": "What a waste.", + "Description": "Cap your hashes." + }, + "SLEEVE_8": { + "ID": "SLEEVE_8", + "Name": "You and what army?", + "Description": "Purchase all duplicate sleeves from The Covenant." + }, + "INDECISIVE": { + "ID": "INDECISIVE", + "Name": "Too many options.", + "Description": "Spend 1h straight on the bitverse." + }, + "FAST_BN": { + "ID": "FAST_BN", + "Name": "Speed demon.", + "Description": "Destroy a bitnode in under 2 days." + }, + "CHALLENGE_BN1": { + "ID": "CHALLENGE_BN1", + "Name": "BN1: Challenge", + "Description": "Destroy BN1 with at most 128GB and 1 core." + }, + "CHALLENGE_BN2": { + "ID": "CHALLENGE_BN2", + "Name": "BN2: Challenge", + "Description": "Destroy BN2 without forming a gang." + }, + "CHALLENGE_BN3": { + "ID": "CHALLENGE_BN3", + "Name": "BN3: Challenge", + "Description": "Destroy BN3 without creating corporation." + }, + "CHALLENGE_BN6": { + "ID": "CHALLENGE_BN6", + "Name": "BN6: Challenge", + "Description": "Destroy BN6 without joining the bladeburner division." + }, + "CHALLENGE_BN7": { + "ID": "CHALLENGE_BN7", + "Name": "BN7: Challenge", + "Description": "Destroy BN7 without joining the bladeburner division." + }, + "CHALLENGE_BN8": { + "ID": "CHALLENGE_BN8", + "Name": "BN8: Challenge", + "Description": "Destroy BN8 without purchasing the 4s market data." + }, + "CHALLENGE_BN9": { + "ID": "CHALLENGE_BN9", + "Name": "BN9: Challenge", + "Description": "Destroy BN9 without using hacknet servers." + }, + "CHALLENGE_BN10": { + "ID": "CHALLENGE_BN10", + "Name": "BN10: Challenge", + "Description": "Destroy BN10 without using sleeves." + }, + "CHALLENGE_BN12": { + "ID": "CHALLENGE_BN12", + "Name": "BN12: Challenge", + "Description": "Destroy BN12 50 times." + }, + "BYPASS": { + "ID": "BYPASS", + "Name": "Exploit: bypass", + "Description": "Circumventing the ram cost of document." + }, + "PROTOTYPETAMPERING": { + "ID": "PROTOTYPETAMPERING", + "Name": "Exploit: prototype tampering", + "Description": "Tamper with the Numbers prototype." + }, + "UNCLICKABLE": { + "ID": "UNCLICKABLE", + "Name": "Exploit: unclickable", + "Description": "Click the unclickable." + }, + "UNDOCUMENTEDFUNCTIONCALL": { + "ID": "UNDOCUMENTEDFUNCTIONCALL", + "Name": "Exploit: undocumented", + "Description": "Call the undocumented function." + }, + "TIMECOMPRESSION": { + "ID": "TIMECOMPRESSION", + "Name": "Exploit: time compression", + "Description": "Compress time." + }, + "REALITYALTERATION": { + "ID": "REALITYALTERATION", + "Name": "Exploit: reality alteration", + "Description": "Alter reality." + }, + "N00DLES": { + "ID": "N00DLES", + "Name": "Exploit: noodles", + "Description": "Harness the power of the noodles." + }, + "EDITSAVEFILE": { + "ID": "EDITSAVEFILE", + "Name": "Exploit: edit", + "Description": "Acquire the EditSaveFile Source-File -1" + }, + "UNACHIEVABLE": { + "ID": "UNACHIEVABLE", + "Name": "UNACHIEVABLE", + "Description": "This achievement cannot be unlocked." + }, + "CHALLENGE_BN13": { + "ID": "CHALLENGE_BN13", + "Name": "BN13: Challenge", + "Description": "Complete BN13 without Stanek's Gift." + }, + "DEVMENU": { + "ID": "DEVMENU", + "Name": "Exploit: edit", + "Description": "Open the dev menu." + } + } +} \ No newline at end of file diff --git a/src/Achievements/AchievementEntry.tsx b/src/Achievements/AchievementEntry.tsx new file mode 100644 index 000000000..0d3131a7a --- /dev/null +++ b/src/Achievements/AchievementEntry.tsx @@ -0,0 +1,60 @@ +import React from "react"; + +import { Box, Typography } from "@mui/material"; + +import { Achievement } from "./Achievements"; +import { Settings } from "../Settings/Settings" +import { AchievementIcon } from "./AchievementIcon"; + +interface IProps { + achievement: Achievement; + unlockedOn?: number; + cssFiltersUnlocked: string; + cssFiltersLocked: string; +} + +export function AchievementEntry({ achievement, unlockedOn, cssFiltersUnlocked, cssFiltersLocked }: IProps): JSX.Element { + if (!achievement) return <>; + const isUnlocked = !!unlockedOn; + + const mainColor = isUnlocked ? Settings.theme.primary : Settings.theme.secondarylight; + + let achievedOn = ''; + if (unlockedOn) { + achievedOn = new Date(unlockedOn).toLocaleString(); + } + + return ( + + + + + + {achievement.Name} + + + {achievement.Description} + + {isUnlocked && ( + + Acquired on {achievedOn} + + )} + + + + ); +} diff --git a/src/Achievements/AchievementIcon.tsx b/src/Achievements/AchievementIcon.tsx new file mode 100644 index 000000000..7bf576b9b --- /dev/null +++ b/src/Achievements/AchievementIcon.tsx @@ -0,0 +1,35 @@ +import React, { useState } from "react"; + +import { Box } from "@mui/material"; + +import { Achievement } from "./Achievements"; +import { Settings } from "../Settings/Settings" + +interface IProps { + achievement: Achievement; + unlocked: boolean; + colorFilters: string; + size: string; +} + +export function AchievementIcon({ achievement, unlocked, colorFilters, size }: IProps): JSX.Element { + const [imgLoaded, setImgLoaded] = useState(false); + const mainColor = unlocked ? Settings.theme.primarydark : Settings.theme.secondarydark; + + if (!achievement.Icon) return (<>); + return ( + + setImgLoaded(true)} + alt={achievement.Name} /> + + ); +} diff --git a/src/Achievements/AchievementList.tsx b/src/Achievements/AchievementList.tsx new file mode 100644 index 000000000..b590cf38c --- /dev/null +++ b/src/Achievements/AchievementList.tsx @@ -0,0 +1,121 @@ +import React from "react"; + +import { Accordion, AccordionSummary, AccordionDetails, Box, Typography } from "@mui/material"; + +import { AchievementEntry } from "./AchievementEntry"; +import { Achievement, PlayerAchievement} from "./Achievements"; +import { Settings } from "../Settings/Settings" +import { getFiltersFromHex } from "../ThirdParty/colorUtils"; +import { CorruptableText } from "../ui/React/CorruptableText"; + +interface IProps { + achievements: Achievement[]; + playerAchievements: PlayerAchievement[]; +} + +export function AchievementList({ achievements, playerAchievements }: IProps): JSX.Element { + // Need to transform the primary color into css filters to change the color of the SVG. + const cssPrimary = getFiltersFromHex(Settings.theme.primary); + const cssSecondary = getFiltersFromHex(Settings.theme.secondary); + + const data = achievements.map(achievement => ({ + achievement, + unlockedOn: playerAchievements.find(playerAchievement => playerAchievement.ID === achievement.ID)?.unlockedOn, + })).sort((a, b) => (b.unlockedOn ?? 0) - (a.unlockedOn ?? 0)); + + const unlocked = data.filter(entry => entry.unlockedOn); + + // Hidden achievements + const secret = data.filter(entry => !entry.unlockedOn && entry.achievement.Secret) + + // Locked behind locked content (bitnode x) + const unavailable = data.filter(entry => !entry.unlockedOn && !entry.achievement.Secret && entry.achievement.Visible && entry.achievement.Visible()); + + // Remaining achievements + const locked = data + .filter(entry => !unlocked.map(u => u.achievement.ID).includes(entry.achievement.ID)) + .filter(entry => !secret.map(u => u.achievement.ID).includes(entry.achievement.ID)) + .filter(entry => !unavailable.map(u => u.achievement.ID).includes(entry.achievement.ID)); + + return ( + + + {unlocked.length > 0 && ( + + + + Acquired ({unlocked.length}/{data.length}) + + + + {unlocked.map(item => ( + + ))} + + + )} + + {locked.length > 0 && ( + + + + Locked ({locked.length} remaining) + + + + {locked.map(item => ( + + ))} + + + )} + + {unavailable.length > 0 && ( + + + + Unavailable ({unavailable.length} remaining) + + + + + {unavailable.length} additional achievements hidden behind content you don't have access to. + + + + )} + + {secret.length > 0 && ( + + + + Secret ({secret.length} remaining) + + + + + {secret.map(item => ( + <> + +
+ + ))} +
+
+
+ )} +
+
+ ); +} diff --git a/src/Achievements/Achievements.ts b/src/Achievements/Achievements.ts new file mode 100644 index 000000000..368c6bb15 --- /dev/null +++ b/src/Achievements/Achievements.ts @@ -0,0 +1,768 @@ +import { PlayerObject } from "src/PersonObjects/Player/PlayerObject"; +import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; +import { SkillNames } from "../Bladeburner/data/SkillNames"; +import { Skills } from "../Bladeburner/Skills"; +import { CONSTANTS } from "../Constants"; +import { Industries } from "../Corporation/IndustryData"; +import { Exploit } from "../Exploits/Exploit"; +import { Factions } from "../Faction/Factions"; +import { AllGangs } from "../Gang/AllGangs"; +import { GangConstants } from "../Gang/data/Constants"; +import { HacknetNodeConstants, HacknetServerConstants } from "../Hacknet/data/Constants"; +import { hasHacknetServers } from "../Hacknet/HacknetHelpers"; +import { HacknetNode } from "../Hacknet/HacknetNode"; +import { HacknetServer } from "../Hacknet/HacknetServer"; +import { CityName } from "../Locations/data/CityNames"; +import { Player } from "../Player"; +import { Programs } from "../Programs/Programs"; +import { GetAllServers, GetServer } from "../Server/AllServers"; +import { SpecialServers } from "../Server/data/SpecialServers"; +import { Server } from "../Server/Server"; +import { Router } from "../ui/GameRoot"; +import { Page } from "../ui/Router"; +import { IMap } from '../types'; +import * as data from "./AchievementData.json"; + +// Unable to correctly cast the JSON data into AchievementDataJson type otherwise... +const achievementData = (data).achievements; + +export interface Achievement { + ID: string; + Icon?: string; + Name?: string; + Description?: string; + Secret?: boolean; + Condition: () => boolean; + Visible?: () => boolean; +} + +export interface PlayerAchievement { + ID: string; + unlockedOn?: number; +} + +export interface AchievementDataJson { + achievements: IMap; +} + +export interface AchievementData { + ID: string; + Name: string; + Description: string; +} + +function bitNodeFinishedState(): boolean { + const wd = GetServer(SpecialServers.WorldDaemon); + if (!(wd instanceof Server)) return false; + if (wd.backdoorInstalled) return true; + return Player.bladeburner !== null && Player.bladeburner.blackops.hasOwnProperty("Operation Daedalus"); +} + +function hasAccessToSF(player: PlayerObject, bn: number): boolean { + return player.bitNodeN === bn || player.sourceFiles.some((a) => a.n === bn); +} + +function knowsAboutBitverse(player: PlayerObject): boolean { + return player.sourceFiles.some((a) => a.n === 1) +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function sfAchievement(): Achievement[] { + const achs: Achievement[] = []; + for (let i = 0; i <= 11; i++) { + for (let j = 1; j <= 3; j++) { + achs.push({ + ID: `SF${i}.${j}`, + Condition: () => Player.sourceFileLvl(i) >= j, + }); + } + } + return achs; +} + +export const achievements: IMap = { + "CYBERSEC": { + ...achievementData['CYBERSEC'], + Icon: "CSEC", + Condition: () => Player.factions.includes("CyberSec"), + }, + "NITESEC": { + ...achievementData['NITESEC'], + Icon: "NiteSec", + Condition: () => Player.factions.includes("NiteSec"), + }, + "THE_BLACK_HAND": { + ...achievementData['THE_BLACK_HAND'], + Icon: "TBH", + Condition: () => Player.factions.includes("The Black Hand"), + }, + "BITRUNNERS": { + ...achievementData['BITRUNNERS'], + Icon: 'bitrunners', + Condition: () => Player.factions.includes("BitRunners"), + }, + "DAEDALUS": { + ...achievementData['DAEDALUS'], + Icon: "daedalus", + Condition: () => Player.factions.includes("Daedalus"), + }, + "THE_COVENANT": { + ...achievementData['THE_COVENANT'], + Icon: "thecovenant", + Condition: () => Player.factions.includes("The Covenant"), + }, + "ILLUMINATI": { + ...achievementData['ILLUMINATI'], + Icon: 'illuminati', + Condition: () => Player.factions.includes("Illuminati") , + }, + "BRUTESSH.EXE": { + ...achievementData['BRUTESSH.EXE'], + Icon: 'p0', + Condition: () => Player.getHomeComputer().programs.includes(Programs.BruteSSHProgram.name), + }, + "FTPCRACK.EXE": { + ...achievementData['FTPCRACK.EXE'], + Icon: 'p1', + Condition: () => Player.getHomeComputer().programs.includes(Programs.FTPCrackProgram.name), + }, + //----------------------------------------------------- + "RELAYSMTP.EXE": { + ...achievementData['RELAYSMTP.EXE'], + Icon: 'p2', + Condition: () => Player.getHomeComputer().programs.includes(Programs.RelaySMTPProgram.name), + }, + "HTTPWORM.EXE": { + ...achievementData['HTTPWORM.EXE'], + Icon: 'p3', + Condition: () => Player.getHomeComputer().programs.includes(Programs.HTTPWormProgram.name), + }, + "SQLINJECT.EXE": { + ...achievementData['SQLINJECT.EXE'], + Icon: 'p4', + Condition: () => Player.getHomeComputer().programs.includes(Programs.SQLInjectProgram.name), + }, + "FORMULAS.EXE": { + ...achievementData['FORMULAS.EXE'], + Icon: 'formulas', + Condition: () => Player.getHomeComputer().programs.includes(Programs.Formulas.name), + }, + "SF1.1": { + ...achievementData['SF1.1'], + Icon: 'SF1.1', + Visible: () => hasAccessToSF(Player, 1), + Condition: () => Player.sourceFileLvl(1) >= 1 + }, + "SF2.1": { + ...achievementData['SF2.1'], + Icon: "SF2.1", + Visible: () => hasAccessToSF(Player, 2), + Condition: () => Player.sourceFileLvl(2) >= 1, + }, + "SF3.1": { + ...achievementData['SF3.1'], + Icon: "SF3.1", + Visible: () => hasAccessToSF(Player, 3), + Condition: () => Player.sourceFileLvl(3) >= 1, + }, + "SF4.1": { + ...achievementData['SF4.1'], + Icon: "SF4.1", + Visible: () => hasAccessToSF(Player, 4), + Condition: () => Player.sourceFileLvl(4) >= 1, + }, + "SF5.1": { + ...achievementData['SF5.1'], + Icon: "SF5.1", + Visible: () => hasAccessToSF(Player, 5), + Condition: () => Player.sourceFileLvl(5) >= 1, + }, + "SF6.1": { + ...achievementData['SF6.1'], + Icon: "SF6.1", + Visible: () => hasAccessToSF(Player, 6), + Condition: () => Player.sourceFileLvl(6) >= 1, + }, + "SF7.1": { + ...achievementData['SF7.1'], + Icon: "SF7.1", + Visible: () => hasAccessToSF(Player, 7), + Condition: () => Player.sourceFileLvl(7) >= 1, + }, + "SF8.1": { + ...achievementData['SF8.1'], + Icon: "SF8.1", + Visible: () => hasAccessToSF(Player, 8), + Condition: () => Player.sourceFileLvl(8) >= 1, + }, + "SF9.1": { + ...achievementData['SF9.1'], + Icon: "SF9.1", + Visible: () => hasAccessToSF(Player, 9), + Condition: () => Player.sourceFileLvl(9) >= 1, + }, + "SF10.1": { + ...achievementData['SF10.1'], + Icon: "SF10.1", + Visible: () => hasAccessToSF(Player, 10), + Condition: () => Player.sourceFileLvl(10) >= 1 + }, + "SF11.1": { + ...achievementData['SF11.1'], + Icon: "SF11.1", + Visible: () => hasAccessToSF(Player, 11), + Condition: () => Player.sourceFileLvl(11) >= 1 + }, + "SF12.1": { + ...achievementData['SF12.1'], + Icon: "SF12.1", + Visible: () => hasAccessToSF(Player, 12), + Condition: () => Player.sourceFileLvl(12) >= 1 + }, + "MONEY_1Q": { + ...achievementData['MONEY_1Q'], + Icon: "$1Q", + Condition: () => Player.money >= 1e18, + }, + "MONEY_M1B": { + ...achievementData['MONEY_M1B'], + Icon: "-1b", + Secret: true, + Condition: () => Player.money <= -1e9, + }, + "INSTALL_1": { + ...achievementData['INSTALL_1'], + Icon: "install", + Condition: () => Player.augmentations.length >= 1, + }, + "INSTALL_100": { + ...achievementData['INSTALL_100'], + Icon: "install_100", + Condition: () => Player.augmentations.length >= 100, + }, + "QUEUE_40": { + ...achievementData['QUEUE_40'], + Icon: "queue40", + Condition: () => Player.queuedAugmentations.length >= 40, + }, + "HACKING_100000": { + ...achievementData['HACKING_100000'], + Icon: "hack100000", + Condition: () => Player.hacking >= 100000, + }, + "COMBAT_3000": { + ...achievementData['COMBAT_3000'], + Icon: "combat3000", + Condition: () => + Player.strength >= 3000 && Player.defense >= 3000 && Player.dexterity >= 3000 && Player.agility >= 3000, + }, + "NEUROFLUX_255": { + ...achievementData['NEUROFLUX_255'], + Icon: "nf255", + Condition: () => Player.augmentations.some((a) => a.name === AugmentationNames.NeuroFluxGovernor && a.level >= 255), + }, + "NS2": { + ...achievementData['NS2'], + Icon: "ns2", + Condition: () => Player.getHomeComputer().scripts.some((s) => s.filename.endsWith(".js") || s.filename.endsWith(".ns")), + }, + "FROZE": { + ...achievementData['FROZE'], + Icon: "forze", + Condition: () => location.href.includes("noScripts") + }, + "RUNNING_SCRIPTS_1000": { + ...achievementData['RUNNING_SCRIPTS_1000'], + Icon: "run1000", + Condition: (): boolean => { + let running = 0; + for (const s of GetAllServers()) { + running += s.runningScripts.length; + } + return running >= 1000; + }, + }, + "DRAIN_SERVER": { + ...achievementData['DRAIN_SERVER'], + Icon: "drain", + Condition: (): boolean => { + for (const s of GetAllServers()) { + if (s instanceof Server) { + if (s.moneyMax > 0 && s.moneyAvailable === 0) return true; + } + } + return false; + }, + }, + "MAX_RAM": { + ...achievementData['MAX_RAM'], + Icon: "maxram", + Condition: () => Player.getHomeComputer().maxRam === CONSTANTS.HomeComputerMaxRam + }, + "MAX_CORES": { + ...achievementData['MAX_CORES'], + Icon: "maxcores", + Condition: () => Player.getHomeComputer().cpuCores === 8 + }, + "SCRIPTS_30": { + ...achievementData['SCRIPTS_30'], + Icon: "folders", + Condition: () => Player.getHomeComputer().scripts.length >= 30 + }, + "KARMA_1000000": { + ...achievementData['KARMA_1000000'], + Icon: "karma", + Secret: true, + Condition: () => Player.karma <= -1e6 + }, + "STOCK_1q": { + ...achievementData['STOCK_1q'], + Icon: "$1Q", + Condition: () => Player.moneySourceB.stock >= 1e15 + }, + "DISCOUNT": { + ...achievementData['DISCOUNT'], + Icon: "discount", + Condition: (): boolean => { + const p = GetServer("powerhouse-fitness"); + if (!(p instanceof Server)) return false; + return p.backdoorInstalled; + }, + }, + "SCRIPT_32GB": { + ...achievementData['SCRIPT_32GB'], + Icon: "bigcost", + Condition: () => Player.getHomeComputer().scripts.some((s) => s.ramUsage >= 32), + }, + "FIRST_HACKNET_NODE": { + ...achievementData['FIRST_HACKNET_NODE'], + Condition: () => !hasHacknetServers(Player) && Player.hacknetNodes.length > 0, + }, + "30_HACKNET_NODE": { + ...achievementData['30_HACKNET_NODE'], + Icon: "hacknet-all", + Condition: () => !hasHacknetServers(Player) && Player.hacknetNodes.length >= 30, + }, + "MAX_HACKNET_NODE": { + ...achievementData['MAX_HACKNET_NODE'], + Icon: "hacknet-max", + Condition: (): boolean => { + if (hasHacknetServers(Player)) return false; + for (const h of Player.hacknetNodes) { + if (!(h instanceof HacknetNode)) return false; + if ( + h.ram === HacknetNodeConstants.MaxRam && + h.cores === HacknetNodeConstants.MaxCores && + h.level === HacknetNodeConstants.MaxLevel + ) + return true; + } + return false; + }, + }, + "HACKNET_NODE_10M": { + ...achievementData['HACKNET_NODE_10M'], + Icon: "hacknet-10m", + Condition: () => !hasHacknetServers(Player) && Player.moneySourceB.hacknet >= 10e6, + }, + "REPUTATION_10M": { + ...achievementData['REPUTATION_10M'], + Icon: "reputation", + Condition: () => Object.values(Factions).some((f) => f.playerReputation >= 10e6), + }, + "DONATION": { + ...achievementData['DONATION'], + Icon: "donation", + Condition: () => Object.values(Factions).some((f) => f.favor >= 150), + }, + "TRAVEL": { + ...achievementData['TRAVEL'], + Icon: "travel", + Condition: () => Player.city !== CityName.Sector12, + }, + "WORKOUT": { + ...achievementData['WORKOUT'], + Icon: "WORKOUT", + Condition: () => + [ + CONSTANTS.ClassGymStrength, + CONSTANTS.ClassGymDefense, + CONSTANTS.ClassGymDexterity, + CONSTANTS.ClassGymAgility, + ].includes(Player.className), + }, + "TOR": { + ...achievementData['TOR'], + Icon: "TOR", + Condition: () => Player.hasTorRouter(), + }, + "HOSPITALIZED": { + ...achievementData['HOSPITALIZED'], + Icon: "OUCH", + Condition: () => Player.moneySourceB.hospitalization !== 0, + }, + "GANG": { + ...achievementData['GANG'], + Icon: "GANG", + Visible: () => hasAccessToSF(Player, 2), + Condition: () => Player.gang !== null, + }, + "FULL_GANG": { + ...achievementData['FULL_GANG'], + Icon: "GANGMAX", + Visible: () => hasAccessToSF(Player, 2), + Condition: () => Player.gang !== null && Player.gang.members.length === GangConstants.MaximumGangMembers, + }, + "GANG_TERRITORY": { + ...achievementData['GANG_TERRITORY'], + Icon: "GANG100%", + Visible: () => hasAccessToSF(Player, 2), + Condition: () => Player.gang !== null && AllGangs[Player.gang.facName].territory >= 0.999, + }, + "GANG_MEMBER_POWER": { + ...achievementData['GANG_MEMBER_POWER'], + Icon: "GANG10000", + Visible: () => hasAccessToSF(Player, 2), + Condition: () => + Player.gang !== null && + Player.gang.members.some((m) => m.hack >= 10000 || m.str >= 10000 || m.def >= 10000 || m.dex >= 10000 || m.agi >= 10000 || m.cha >= 10000), + }, + "CORPORATION": { + ...achievementData['CORPORATION'], + Icon: "CORP", + Visible: () => hasAccessToSF(Player, 3), + Condition: () => Player.corporation !== null, + }, + "CORPORATION_BRIBE": { + ...achievementData['CORPORATION_BRIBE'], + Icon: "CORPLOBBY", + Visible: () => hasAccessToSF(Player, 3), + Condition: () => Player.corporation !== null && Player.corporation.unlockUpgrades[6] === 1, + }, + "CORPORATION_PROD_1000": { + ...achievementData['CORPORATION_PROD_1000'], + Icon: "CORP1000", + Visible: () => hasAccessToSF(Player, 3), + Condition: () => Player.corporation !== null && Player.corporation.divisions.some((d) => d.prodMult >= 1000), + }, + "CORPORATION_EMPLOYEE_3000": { + ...achievementData['CORPORATION_EMPLOYEE_3000'], + Icon: "CORPCITY", + Visible: () => hasAccessToSF(Player, 3), + Condition: (): boolean => { + if (Player.corporation === null) return false; + for (const d of Player.corporation.divisions) { + for (const o of Object.values(d.offices)) { + if (o === 0) continue; + if (o.employees.length > 3000) return true; + } + } + return false; + }, + }, + "CORPORATION_REAL_ESTATE": { + ...achievementData['CORPORATION_REAL_ESTATE'], + Icon: "CORPRE", + Name: "Own the land", + Description: "Expand to the Real Estate division.", + Visible: () => hasAccessToSF(Player, 3), + Condition: () => Player.corporation !== null && Player.corporation.divisions.some((d) => d.type === Industries.RealEstate), + }, + "INTELLIGENCE_255": { + ...achievementData['INTELLIGENCE_255'], + Icon: "INT255", + Visible: () => hasAccessToSF(Player, 5), + Condition: () => Player.intelligence >= 255, + }, + "BLADEBURNER_DIVISION": { + ...achievementData['BLADEBURNER_DIVISION'], + Icon: "BLADE", + Visible: () => hasAccessToSF(Player, 6), + Condition: () => Player.bladeburner !== null, + }, + "BLADEBURNER_OVERCLOCK": { + ...achievementData['BLADEBURNER_OVERCLOCK'], + Icon: "BLADEOVERCLOCK", + Visible: () => hasAccessToSF(Player, 6), + Condition: () => + Player.bladeburner !== null && + Player.bladeburner.skills[SkillNames.Overclock] === Skills[SkillNames.Overclock].maxLvl, + }, + "BLADEBURNER_UNSPENT_100000": { + ...achievementData['BLADEBURNER_UNSPENT_100000'], + Icon: "BLADE100K", + Visible: () => hasAccessToSF(Player, 6), + Condition: () => Player.bladeburner !== null && Player.bladeburner.skillPoints >= 100000, + }, + "4S": { + ...achievementData['4S'], + Icon: "4S", + Condition: () => Player.has4SData + }, + "FIRST_HACKNET_SERVER": { + ...achievementData['FIRST_HACKNET_SERVER'], + Icon: "HASHNET", + Visible: () => hasAccessToSF(Player, 9), + Condition: () => hasHacknetServers(Player) && Player.hacknetNodes.length > 0, + }, + "ALL_HACKNET_SERVER": { + ...achievementData['ALL_HACKNET_SERVER'], + Icon: "HASHNETALL", + Visible: () => hasAccessToSF(Player, 9), + Condition: () => hasHacknetServers(Player) && Player.hacknetNodes.length === HacknetServerConstants.MaxServers, + }, + "MAX_HACKNET_SERVER": { + ...achievementData['MAX_HACKNET_SERVER'], + Icon: "HASHNETALL", + Visible: () => hasAccessToSF(Player, 9), + Condition: (): boolean => { + if (!hasHacknetServers(Player)) return false; + for (const h of Player.hacknetNodes) { + if (typeof h !== "string") return false; + const hs = GetServer(h); + if (!(hs instanceof HacknetServer)) return false; + if ( + hs.maxRam === HacknetServerConstants.MaxRam && + hs.cores === HacknetServerConstants.MaxCores && + hs.level === HacknetServerConstants.MaxLevel && + hs.cache === HacknetServerConstants.MaxCache + ) + return true; + } + return false; + }, + }, + "HACKNET_SERVER_1B": { + ...achievementData['HACKNET_SERVER_1B'], + Icon: "HASHNETMONEY", + Visible: () => hasAccessToSF(Player, 9), + Condition: () => hasHacknetServers(Player) && Player.moneySourceB.hacknet >= 1e9, + }, + "MAX_CACHE": { + ...achievementData['MAX_CACHE'], + Icon: "HASHNETCAP", + Visible: () => hasAccessToSF(Player, 9), + Condition: () => hasHacknetServers(Player) && Player.hashManager.hashes === Player.hashManager.capacity, + }, + "SLEEVE_8": { + ...achievementData['SLEEVE_8'], + Icon: "SLEEVE8", + Visible: () => hasAccessToSF(Player, 10), + Condition: () => Player.sleeves.length === 8, + }, + "INDECISIVE": { + ...achievementData['INDECISIVE'], + Icon: "1H", + Visible: () => knowsAboutBitverse(Player), + Condition: (function () { + let c = 0; + setInterval(() => { + if (Router.page() === Page.BitVerse) { + c++; + } else { + c = 0; + } + }, 60 * 1000); + return () => c > 60; + })(), + }, + "FAST_BN": { + ...achievementData['FAST_BN'], + Icon: "2DAYS", + Visible: () => knowsAboutBitverse(Player), + Condition: () => bitNodeFinishedState() && Player.playtimeSinceLastBitnode < 1000 * 60 * 60 * 24 * 2, + }, + "CHALLENGE_BN1": { + ...achievementData['CHALLENGE_BN1'], + Icon: "BN1+", + Visible: () => knowsAboutBitverse(Player), + Condition: () => + Player.bitNodeN === 1 && + bitNodeFinishedState() && + Player.getHomeComputer().maxRam <= 128 && + Player.getHomeComputer().cpuCores === 1, + }, + "CHALLENGE_BN2": { + ...achievementData['CHALLENGE_BN2'], + Icon: "BN2+", + Visible: () => hasAccessToSF(Player, 2), + Condition: () => Player.bitNodeN === 2 && bitNodeFinishedState() && Player.gang === null, + }, + "CHALLENGE_BN3": { + ...achievementData['CHALLENGE_BN3'], + Icon: "BN3+", + Visible: () => hasAccessToSF(Player, 3), + Condition: () => Player.bitNodeN === 3 && bitNodeFinishedState() && Player.corporation === null, + }, + "CHALLENGE_BN6": { + ...achievementData['CHALLENGE_BN6'], + Icon: "BN6+", + Visible: () => hasAccessToSF(Player, 6), + Condition: () => Player.bitNodeN === 6 && bitNodeFinishedState() && Player.bladeburner === null, + }, + "CHALLENGE_BN7": { + ...achievementData['CHALLENGE_BN7'], + Icon: "BN7+", + Visible: () => hasAccessToSF(Player, 7), + Condition: () => Player.bitNodeN === 7 && bitNodeFinishedState() && Player.bladeburner === null, + }, + "CHALLENGE_BN8": { + ...achievementData['CHALLENGE_BN8'], + Icon: "BN8+", + Visible: () => hasAccessToSF(Player, 8), + Condition: () => Player.bitNodeN === 8 && bitNodeFinishedState() && !Player.has4SData && !Player.has4SDataTixApi, + }, + "CHALLENGE_BN9": { + ...achievementData['CHALLENGE_BN9'], + Icon: "BN9+", + Visible: () => hasAccessToSF(Player, 9), + Condition: () => + Player.bitNodeN === 9 && + bitNodeFinishedState() && + Player.moneySourceB.hacknet === 0 && + Player.moneySourceB.hacknet_expenses === 0, + }, + "CHALLENGE_BN10": { + ...achievementData['CHALLENGE_BN10'], + Icon: "BN10+", + Visible: () => hasAccessToSF(Player, 10), + Condition: () => + Player.bitNodeN === 10 && + bitNodeFinishedState() && + !Player.sleeves.some( + (s) => + s.augmentations.length > 0 || + s.hacking_exp > 0 || + s.strength_exp > 0 || + s.defense_exp > 0 || + s.agility_exp > 0 || + s.dexterity_exp > 0 || + s.charisma_exp > 0, + ), + }, + "CHALLENGE_BN12": { + ...achievementData['CHALLENGE_BN12'], + Icon: "BN12+", + Visible: () => hasAccessToSF(Player, 12), + Condition: () => Player.sourceFileLvl(12) >= 50 + }, + "BYPASS": { + ...achievementData['BYPASS'], + Icon: "SF-1", + Secret: true, + Condition: () => Player.exploits.includes(Exploit.Bypass) + }, + "PROTOTYPETAMPERING": { + ...achievementData['PROTOTYPETAMPERING'], + Icon: "SF-1", + Secret: true, + Condition: () => Player.exploits.includes(Exploit.PrototypeTampering) + }, + "UNCLICKABLE": { + ...achievementData['UNCLICKABLE'], + Icon: "SF-1", + Secret: true, + Condition: () => Player.exploits.includes(Exploit.Unclickable) + }, + "UNDOCUMENTEDFUNCTIONCALL": { + ...achievementData['UNDOCUMENTEDFUNCTIONCALL'], + Icon: "SF-1", + Secret: true, + Condition: () => Player.exploits.includes(Exploit.UndocumentedFunctionCall) + }, + "TIMECOMPRESSION": { + ...achievementData['TIMECOMPRESSION'], + Icon: "SF-1", + Secret: true, + Condition: () => Player.exploits.includes(Exploit.TimeCompression) + }, + "REALITYALTERATION": { + ...achievementData['REALITYALTERATION'], + Icon: "SF-1", + Secret: true, + Condition: () => Player.exploits.includes(Exploit.RealityAlteration) + }, + "N00DLES": { + ...achievementData['N00DLES'], + Icon: "SF-1", + Secret: true, + Condition: () => Player.exploits.includes(Exploit.N00dles) + }, + "EDITSAVEFILE": { + ...achievementData['EDITSAVEFILE'], + Icon: "SF-1", + Secret: true, + Condition: () => Player.exploits.includes(Exploit.EditSaveFile) + }, + "UNACHIEVABLE": { + ...achievementData['UNACHIEVABLE'], + Icon: "SF-1", + Secret: true, + // Hey Players! Yes, you're supposed to modify this to get the achievement! + Condition: () => false, + }, + "CHALLENGE_BN13": { + ...achievementData['CHALLENGE_BN13'], + Icon: "BN13+", + Visible: () => hasAccessToSF(Player, 13), + Condition: () => + Player.bitNodeN === 13 && + bitNodeFinishedState() && + !Player.augmentations.some((a) => a.name === AugmentationNames.StaneksGift1), + }, + "DEVMENU": { + ...achievementData['DEVMENU'], + Icon: "SF-1", + Condition: () => Player.exploits.includes(Exploit.YoureNotMeantToAccessThis) + } +} + + // Steam has a limit of 100 achievement. So these were planned but commented for now. + // { ID: "ECORP", Condition: () => Player.factions.includes("ECorp") }, + // { ID: "MEGACORP", Condition: () => Player.factions.includes("MegaCorp") }, + // { ID: "BACHMAN_&_ASSOCIATES", Condition: () => Player.factions.includes("Bachman & Associates") }, + // { ID: "BLADE_INDUSTRIES", Condition: () => Player.factions.includes("Blade Industries") }, + // { ID: "NWO", Condition: () => Player.factions.includes("NWO") }, + // { ID: "CLARKE_INCORPORATED", Condition: () => Player.factions.includes("Clarke Incorporated") }, + // { ID: "OMNITEK_INCORPORATED", Condition: () => Player.factions.includes("OmniTek Incorporated") }, + // { ID: "FOUR_SIGMA", Condition: () => Player.factions.includes("Four Sigma") }, + // { ID: "KUAIGONG_INTERNATIONAL", Condition: () => Player.factions.includes("KuaiGong International") }, + // { ID: "FULCRUM_SECRET_TECHNOLOGIES", Condition: () => Player.factions.includes("Fulcrum Secret Technologies") }, + // { ID: "AEVUM", Condition: () => Player.factions.includes("Aevum") }, + // { ID: "CHONGQING", Condition: () => Player.factions.includes("Chongqing") }, + // { ID: "ISHIMA", Condition: () => Player.factions.includes("Ishima") }, + // { ID: "NEW_TOKYO", Condition: () => Player.factions.includes("New Tokyo") }, + // { ID: "SECTOR-12", Condition: () => Player.factions.includes("Sector-12") }, + // { ID: "VOLHAVEN", Condition: () => Player.factions.includes("Volhaven") }, + // { ID: "SPEAKERS_FOR_THE_DEAD", Condition: () => Player.factions.includes("Speakers for the Dead") }, + // { ID: "THE_DARK_ARMY", Condition: () => Player.factions.includes("The Dark Army") }, + // { ID: "THE_SYNDICATE", Condition: () => Player.factions.includes("The Syndicate") }, + // { ID: "SILHOUETTE", Condition: () => Player.factions.includes("Silhouette") }, + // { ID: "TETRADS", Condition: () => Player.factions.includes("Tetrads") }, + // { ID: "SLUM_SNAKES", Condition: () => Player.factions.includes("Slum Snakes") }, + // { ID: "NETBURNERS", Condition: () => Player.factions.includes("Netburners") }, + // { ID: "TIAN_DI_HUI", Condition: () => Player.factions.includes("Tian Di Hui") }, + // { ID: "BLADEBURNERS", Condition: () => Player.factions.includes("Bladeburners") }, + // { ID: "DEEPSCANV1.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.DeepscanV1.name) }, + // { ID: "DEEPSCANV2.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.DeepscanV2.name) }, + // { + // ID: "SERVERPROFILER.EXE", + // Condition: () => Player.getHomeComputer().programs.includes(Programs.ServerProfiler.name), + // }, + // { ID: "AUTOLINK.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.AutoLink.name) }, + // { ID: "FLIGHT.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.Flight.name) }, + +export function calculateAchievements(): void { + const availableAchievements = Object.values(achievements).filter((a) => a.Condition()).map((a) => a.ID); + const playerAchievements = Player.achievements.map((a) => a.ID); + const newAchievements = availableAchievements.filter(a => !playerAchievements.includes(a)); + + for (const id of newAchievements) { + Player.giveAchievement(id); + } + + // Write all player's achievements to document for Steam/Electron + // This could be replaced by "availableAchievements" + // if we don't want to grant the save game achievements to steam but only currently available + (document as any).achievements = [...Player.achievements.map(a => a.ID)]; +} diff --git a/src/Achievements/AchievementsRoot.tsx b/src/Achievements/AchievementsRoot.tsx new file mode 100644 index 000000000..12bf0cd93 --- /dev/null +++ b/src/Achievements/AchievementsRoot.tsx @@ -0,0 +1,30 @@ +import React from "react"; + +import makeStyles from "@mui/styles/makeStyles"; +import createStyles from "@mui/styles/createStyles"; +import { Theme } from "@mui/material/styles"; + +import { AchievementList } from "./AchievementList"; +import { achievements } from "./Achievements"; +import { Typography } from "@mui/material"; +import { Player } from "../Player"; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + width: 50, + padding: theme.spacing(2), + userSelect: "none", + }, + }), +); + +export function AchievementsRoot(): JSX.Element { + const classes = useStyles(); + return ( +
+ Achievements + +
+ ); +} diff --git a/src/Achievements/README.md b/src/Achievements/README.md new file mode 100644 index 000000000..414145b13 --- /dev/null +++ b/src/Achievements/README.md @@ -0,0 +1,9 @@ +# Adding Achievements + +* Add a .svg in `./assets/Steam/achievements/real` +* Create the achievement in Steam Dev Portal +* Run `sh ./assets/Steam/achievements/pack-for-web.sh` +* Run `node ./tools/fetch-steam-achievements-data DEVKEYHERE` + * Get your key here: https://steamcommunity.com/dev/apikey +* Add an entry in `./src/Achievements/Achievements.ts` -> achievements +* Commit `./dist/icons/achievements` & `./src/Achievements/AchievementData.json` diff --git a/src/Electron.tsx b/src/Electron.tsx index 4f7221e4f..b4f4ba684 100644 --- a/src/Electron.tsx +++ b/src/Electron.tsx @@ -1,429 +1,18 @@ -import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; -import { SkillNames } from "./Bladeburner/data/SkillNames"; -import { Skills } from "./Bladeburner/Skills"; -import { CONSTANTS } from "./Constants"; -import { Industries } from "./Corporation/IndustryData"; -import { Exploit } from "./Exploits/Exploit"; -import { Factions } from "./Faction/Factions"; -import { AllGangs } from "./Gang/AllGangs"; -import { GangConstants } from "./Gang/data/Constants"; -import { HacknetNodeConstants, HacknetServerConstants } from "./Hacknet/data/Constants"; -import { hasHacknetServers } from "./Hacknet/HacknetHelpers"; -import { HacknetNode } from "./Hacknet/HacknetNode"; -import { HacknetServer } from "./Hacknet/HacknetServer"; -import { CityName } from "./Locations/data/CityNames"; import { Player } from "./Player"; -import { Programs } from "./Programs/Programs"; import { isScriptFilename } from "./Script/isScriptFilename"; import { Script } from "./Script/Script"; -import { GetAllServers, GetServer } from "./Server/AllServers"; -import { SpecialServers } from "./Server/data/SpecialServers"; -import { Server } from "./Server/Server"; -import { Router } from "./ui/GameRoot"; -import { Page } from "./ui/Router"; import { removeLeadingSlash } from "./Terminal/DirectoryHelpers"; import { Terminal } from "./Terminal"; import { SnackbarEvents } from "./ui/React/Snackbar"; import { IMap } from "./types"; - -interface Achievement { - ID: string; - Condition: () => boolean; -} - -function bitNodeFinishedState(): boolean { - const wd = GetServer(SpecialServers.WorldDaemon); - if (!(wd instanceof Server)) return false; - if (wd.backdoorInstalled) return true; - return Player.bladeburner !== null && Player.bladeburner.blackops.hasOwnProperty("Operation Daedalus"); -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function sfAchievement(): Achievement[] { - const achs: Achievement[] = []; - for (let i = 0; i <= 11; i++) { - for (let j = 1; j <= 3; j++) { - achs.push({ - ID: `SF${i}.${j}`, - Condition: () => Player.sourceFileLvl(i) >= j, - }); - } - } - return achs; -} - -const achievements: Achievement[] = [ - { ID: "CYBERSEC", Condition: () => Player.factions.includes("CyberSec") }, - { ID: "NITESEC", Condition: () => Player.factions.includes("NiteSec") }, - { ID: "THE_BLACK_HAND", Condition: () => Player.factions.includes("The Black Hand") }, - { ID: "BITRUNNERS", Condition: () => Player.factions.includes("BitRunners") }, - { ID: "THE_COVENANT", Condition: () => Player.factions.includes("The Covenant") }, - { ID: "DAEDALUS", Condition: () => Player.factions.includes("Daedalus") }, - { ID: "ILLUMINATI", Condition: () => Player.factions.includes("Illuminati") }, - { ID: "BRUTESSH.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.BruteSSHProgram.name) }, - { ID: "FTPCRACK.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.FTPCrackProgram.name) }, - { ID: "RELAYSMTP.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.RelaySMTPProgram.name) }, - { ID: "HTTPWORM.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.HTTPWormProgram.name) }, - { ID: "SQLINJECT.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.SQLInjectProgram.name) }, - { ID: "FORMULAS.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.Formulas.name) }, - { ID: "SF1.1", Condition: () => Player.sourceFileLvl(1) >= 1 }, - { ID: "SF2.1", Condition: () => Player.sourceFileLvl(2) >= 1 }, - { ID: "SF3.1", Condition: () => Player.sourceFileLvl(3) >= 1 }, - { ID: "SF4.1", Condition: () => Player.sourceFileLvl(4) >= 1 }, - { ID: "SF5.1", Condition: () => Player.sourceFileLvl(5) >= 1 }, - { ID: "SF6.1", Condition: () => Player.sourceFileLvl(6) >= 1 }, - { ID: "SF7.1", Condition: () => Player.sourceFileLvl(7) >= 1 }, - { ID: "SF8.1", Condition: () => Player.sourceFileLvl(8) >= 1 }, - { ID: "SF9.1", Condition: () => Player.sourceFileLvl(9) >= 1 }, - { ID: "SF10.1", Condition: () => Player.sourceFileLvl(10) >= 1 }, - { ID: "SF11.1", Condition: () => Player.sourceFileLvl(11) >= 1 }, - { ID: "SF12.1", Condition: () => Player.sourceFileLvl(12) >= 1 }, - { - ID: "MONEY_1Q", - Condition: () => Player.money >= 1e18, - }, - { - ID: "MONEY_M1B", - Condition: () => Player.money <= -1e9, - }, - { - ID: "INSTALL_1", - Condition: () => Player.augmentations.length >= 1, - }, - { - ID: "INSTALL_100", - Condition: () => Player.augmentations.length >= 100, - }, - { - ID: "QUEUE_40", - Condition: () => Player.queuedAugmentations.length >= 40, - }, - { - ID: "HACKING_100000", - Condition: () => Player.hacking >= 100000, - }, - { - ID: "COMBAT_3000", - Condition: () => - Player.strength >= 3000 && Player.defense >= 3000 && Player.dexterity >= 3000 && Player.agility >= 3000, - }, - { - ID: "NEUROFLUX_255", - Condition: () => Player.augmentations.some((a) => a.name === AugmentationNames.NeuroFluxGovernor && a.level >= 255), - }, - { - ID: "NS2", - Condition: () => - Player.getHomeComputer().scripts.some((s) => s.filename.endsWith(".js") || s.filename.endsWith(".ns")), - }, - { ID: "FROZE", Condition: () => location.href.includes("noScripts") }, - { - ID: "RUNNING_SCRIPTS_1000", - Condition: () => { - let running = 0; - for (const s of GetAllServers()) { - running += s.runningScripts.length; - } - return running >= 1000; - }, - }, - { - ID: "DRAIN_SERVER", - Condition: () => { - for (const s of GetAllServers()) { - if (s instanceof Server) { - if (s.moneyMax > 0 && s.moneyAvailable === 0) return true; - } - } - return false; - }, - }, - { ID: "MAX_RAM", Condition: () => Player.getHomeComputer().maxRam === CONSTANTS.HomeComputerMaxRam }, - { ID: "MAX_CORES", Condition: () => Player.getHomeComputer().cpuCores === 8 }, - { ID: "SCRIPTS_30", Condition: () => Player.getHomeComputer().scripts.length >= 30 }, - { ID: "KARMA_1000000", Condition: () => Player.karma <= -1e6 }, - { ID: "STOCK_1q", Condition: () => Player.moneySourceB.stock >= 1e15 }, - { - ID: "DISCOUNT", - Condition: () => { - const p = GetServer("powerhouse-fitness"); - if (!(p instanceof Server)) return false; - return p.backdoorInstalled; - }, - }, - { ID: "SCRIPT_32GB", Condition: () => Player.getHomeComputer().scripts.some((s) => s.ramUsage >= 32) }, - { ID: "FIRST_HACKNET_NODE", Condition: () => !hasHacknetServers(Player) && Player.hacknetNodes.length > 0 }, - { - ID: "30_HACKNET_NODE", - Condition: () => !hasHacknetServers(Player) && Player.hacknetNodes.length >= 30, - }, - { - ID: "MAX_HACKNET_NODE", - Condition: () => { - if (hasHacknetServers(Player)) return false; - for (const h of Player.hacknetNodes) { - if (!(h instanceof HacknetNode)) return false; - if ( - h.ram === HacknetNodeConstants.MaxRam && - h.cores === HacknetNodeConstants.MaxCores && - h.level === HacknetNodeConstants.MaxLevel - ) - return true; - } - return false; - }, - }, - { ID: "HACKNET_NODE_10M", Condition: () => !hasHacknetServers(Player) && Player.moneySourceB.hacknet >= 10e6 }, - { ID: "REPUTATION_10M", Condition: () => Object.values(Factions).some((f) => f.playerReputation >= 10e6) }, - { ID: "DONATION", Condition: () => Object.values(Factions).some((f) => f.favor >= 150) }, - { ID: "TRAVEL", Condition: () => Player.city !== CityName.Sector12 }, - { - ID: "WORKOUT", - Condition: () => - [ - CONSTANTS.ClassGymStrength, - CONSTANTS.ClassGymDefense, - CONSTANTS.ClassGymDexterity, - CONSTANTS.ClassGymAgility, - ].includes(Player.className), - }, - { ID: "TOR", Condition: () => Player.hasTorRouter() }, - { ID: "HOSPITALIZED", Condition: () => Player.moneySourceB.hospitalization !== 0 }, - { ID: "GANG", Condition: () => Player.gang !== null }, - { - ID: "FULL_GANG", - Condition: () => Player.gang !== null && Player.gang.members.length === GangConstants.MaximumGangMembers, - }, - { - ID: "GANG_TERRITORY", - Condition: () => Player.gang !== null && AllGangs[Player.gang.facName].territory >= 0.999, - }, - { - ID: "GANG_MEMBER_POWER", - Condition: () => - Player.gang !== null && - Player.gang.members.some( - (m) => - m.hack >= 10000 || m.str >= 10000 || m.def >= 10000 || m.dex >= 10000 || m.agi >= 10000 || m.cha >= 10000, - ), - }, - { ID: "CORPORATION", Condition: () => Player.corporation !== null }, - { - ID: "CORPORATION_BRIBE", - Condition: () => Player.corporation !== null && Player.corporation.unlockUpgrades[6] === 1, - }, - { - ID: "CORPORATION_PROD_1000", - Condition: () => Player.corporation !== null && Player.corporation.divisions.some((d) => d.prodMult >= 1000), - }, - { - ID: "CORPORATION_EMPLOYEE_3000", - Condition: () => { - if (Player.corporation === null) return false; - for (const d of Player.corporation.divisions) { - for (const o of Object.values(d.offices)) { - if (o === 0) continue; - if (o.employees.length > 3000) return true; - } - } - return false; - }, - }, - { - ID: "CORPORATION_REAL_ESTATE", - Condition: () => - Player.corporation !== null && Player.corporation.divisions.some((d) => d.type === Industries.RealEstate), - }, - { ID: "INTELLIGENCE_255", Condition: () => Player.intelligence >= 255 }, - { ID: "BLADEBURNER_DIVISION", Condition: () => Player.bladeburner !== null }, - { - ID: "BLADEBURNER_OVERCLOCK", - Condition: () => - Player.bladeburner !== null && - Player.bladeburner.skills[SkillNames.Overclock] === Skills[SkillNames.Overclock].maxLvl, - }, - { - ID: "BLADEBURNER_UNSPENT_100000", - Condition: () => Player.bladeburner !== null && Player.bladeburner.skillPoints >= 100000, - }, - { ID: "4S", Condition: () => Player.has4SData }, - { ID: "FIRST_HACKNET_SERVER", Condition: () => hasHacknetServers(Player) && Player.hacknetNodes.length > 0 }, - { - ID: "ALL_HACKNET_SERVER", - Condition: () => hasHacknetServers(Player) && Player.hacknetNodes.length === HacknetServerConstants.MaxServers, - }, - { - ID: "MAX_HACKNET_SERVER", - Condition: () => { - if (!hasHacknetServers(Player)) return false; - for (const h of Player.hacknetNodes) { - if (typeof h !== "string") return false; - const hs = GetServer(h); - if (!(hs instanceof HacknetServer)) return false; - if ( - hs.maxRam === HacknetServerConstants.MaxRam && - hs.cores === HacknetServerConstants.MaxCores && - hs.level === HacknetServerConstants.MaxLevel && - hs.cache === HacknetServerConstants.MaxCache - ) - return true; - } - return false; - }, - }, - { ID: "HACKNET_SERVER_1B", Condition: () => hasHacknetServers(Player) && Player.moneySourceB.hacknet >= 1e9 }, - { - ID: "MAX_CACHE", - Condition: () => hasHacknetServers(Player) && Player.hashManager.hashes === Player.hashManager.capacity, - }, - { - ID: "SLEEVE_8", - Condition: () => Player.sleeves.length === 8, - }, - { - ID: "FAST_BN", - Condition: () => bitNodeFinishedState() && Player.playtimeSinceLastBitnode < 1000 * 60 * 60 * 24 * 2, - }, - { - ID: "INDECISIVE", - Condition: (function () { - let c = 0; - setInterval(() => { - if (Router.page() === Page.BitVerse) { - c++; - } else { - c = 0; - } - }, 60 * 1000); - return () => c > 60; - })(), - }, - { - ID: "CHALLENGE_BN1", - Condition: () => - Player.bitNodeN === 1 && - bitNodeFinishedState() && - Player.getHomeComputer().maxRam <= 128 && - Player.getHomeComputer().cpuCores === 1, - }, - { - ID: "CHALLENGE_BN2", - Condition: () => Player.bitNodeN === 2 && bitNodeFinishedState() && Player.gang === null, - }, - { - ID: "CHALLENGE_BN3", - Condition: () => Player.bitNodeN === 3 && bitNodeFinishedState() && Player.corporation === null, - }, - { - ID: "CHALLENGE_BN6", - Condition: () => Player.bitNodeN === 6 && bitNodeFinishedState() && Player.bladeburner === null, - }, - { - ID: "CHALLENGE_BN7", - Condition: () => Player.bitNodeN === 7 && bitNodeFinishedState() && Player.bladeburner === null, - }, - { - ID: "CHALLENGE_BN8", - Condition: () => Player.bitNodeN === 8 && bitNodeFinishedState() && !Player.has4SData && !Player.has4SDataTixApi, - }, - { - ID: "CHALLENGE_BN9", - Condition: () => - Player.bitNodeN === 9 && - bitNodeFinishedState() && - Player.moneySourceB.hacknet === 0 && - Player.moneySourceB.hacknet_expenses === 0, - }, - { - ID: "CHALLENGE_BN10", - Condition: () => - Player.bitNodeN === 10 && - bitNodeFinishedState() && - !Player.sleeves.some( - (s) => - s.augmentations.length > 0 || - s.hacking_exp > 0 || - s.strength_exp > 0 || - s.defense_exp > 0 || - s.agility_exp > 0 || - s.dexterity_exp > 0 || - s.charisma_exp > 0, - ), - }, - { ID: "CHALLENGE_BN12", Condition: () => Player.sourceFileLvl(12) >= 50 }, - { - ID: "CHALLENGE_BN13", - Condition: () => - Player.bitNodeN === 13 && - bitNodeFinishedState() && - !Player.augmentations.some((a) => a.name === AugmentationNames.StaneksGift1), - }, - { ID: "BYPASS", Condition: () => Player.exploits.includes(Exploit.Bypass) }, - { ID: "PROTOTYPETAMPERING", Condition: () => Player.exploits.includes(Exploit.PrototypeTampering) }, - { ID: "UNCLICKABLE", Condition: () => Player.exploits.includes(Exploit.Unclickable) }, - { ID: "UNDOCUMENTEDFUNCTIONCALL", Condition: () => Player.exploits.includes(Exploit.UndocumentedFunctionCall) }, - { ID: "TIMECOMPRESSION", Condition: () => Player.exploits.includes(Exploit.TimeCompression) }, - { ID: "REALITYALTERATION", Condition: () => Player.exploits.includes(Exploit.RealityAlteration) }, - { ID: "N00DLES", Condition: () => Player.exploits.includes(Exploit.N00dles) }, - { ID: "EDITSAVEFILE", Condition: () => Player.exploits.includes(Exploit.EditSaveFile) }, - { ID: "DEVMENU", Condition: () => Player.exploits.includes(Exploit.YoureNotMeantToAccessThis) }, - { - ID: "UNACHIEVABLE", - // Hey Players! Yes, you're supposed to modify this to get the achievement! - Condition: () => false, - }, - - // Steam has a limit of 100 achievement. So these were planned but commented for now. - // { ID: "ECORP", Condition: () => Player.factions.includes("ECorp") }, - // { ID: "MEGACORP", Condition: () => Player.factions.includes("MegaCorp") }, - // { ID: "BACHMAN_&_ASSOCIATES", Condition: () => Player.factions.includes("Bachman & Associates") }, - // { ID: "BLADE_INDUSTRIES", Condition: () => Player.factions.includes("Blade Industries") }, - // { ID: "NWO", Condition: () => Player.factions.includes("NWO") }, - // { ID: "CLARKE_INCORPORATED", Condition: () => Player.factions.includes("Clarke Incorporated") }, - // { ID: "OMNITEK_INCORPORATED", Condition: () => Player.factions.includes("OmniTek Incorporated") }, - // { ID: "FOUR_SIGMA", Condition: () => Player.factions.includes("Four Sigma") }, - // { ID: "KUAIGONG_INTERNATIONAL", Condition: () => Player.factions.includes("KuaiGong International") }, - // { ID: "FULCRUM_SECRET_TECHNOLOGIES", Condition: () => Player.factions.includes("Fulcrum Secret Technologies") }, - // { ID: "AEVUM", Condition: () => Player.factions.includes("Aevum") }, - // { ID: "CHONGQING", Condition: () => Player.factions.includes("Chongqing") }, - // { ID: "ISHIMA", Condition: () => Player.factions.includes("Ishima") }, - // { ID: "NEW_TOKYO", Condition: () => Player.factions.includes("New Tokyo") }, - // { ID: "SECTOR-12", Condition: () => Player.factions.includes("Sector-12") }, - // { ID: "VOLHAVEN", Condition: () => Player.factions.includes("Volhaven") }, - // { ID: "SPEAKERS_FOR_THE_DEAD", Condition: () => Player.factions.includes("Speakers for the Dead") }, - // { ID: "THE_DARK_ARMY", Condition: () => Player.factions.includes("The Dark Army") }, - // { ID: "THE_SYNDICATE", Condition: () => Player.factions.includes("The Syndicate") }, - // { ID: "SILHOUETTE", Condition: () => Player.factions.includes("Silhouette") }, - // { ID: "TETRADS", Condition: () => Player.factions.includes("Tetrads") }, - // { ID: "SLUM_SNAKES", Condition: () => Player.factions.includes("Slum Snakes") }, - // { ID: "NETBURNERS", Condition: () => Player.factions.includes("Netburners") }, - // { ID: "TIAN_DI_HUI", Condition: () => Player.factions.includes("Tian Di Hui") }, - // { ID: "BLADEBURNERS", Condition: () => Player.factions.includes("Bladeburners") }, - // { ID: "DEEPSCANV1.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.DeepscanV1.name) }, - // { ID: "DEEPSCANV2.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.DeepscanV2.name) }, - // { - // ID: "SERVERPROFILER.EXE", - // Condition: () => Player.getHomeComputer().programs.includes(Programs.ServerProfiler.name), - // }, - // { ID: "AUTOLINK.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.AutoLink.name) }, - // { ID: "FLIGHT.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.Flight.name) }, -]; - -function setAchievements(achs: string[]): void { - (document as any).achievements = achs; -} - -function calculateAchievements(): void { - setAchievements(achievements.filter((a) => a.Condition()).map((a) => a.ID)); -} +import { GetServer } from "./Server/AllServers"; export function initElectron(): void { const userAgent = navigator.userAgent.toLowerCase(); if (userAgent.indexOf(" electron/") > -1) { // Electron-specific code - setAchievements([]); + (document as any).achievements = []; initWebserver(); - setInterval(calculateAchievements, 5000); initAppNotifier(); } } diff --git a/src/IEngine.ts b/src/IEngine.ts index d495e5e20..5f99fdf97 100644 --- a/src/IEngine.ts +++ b/src/IEngine.ts @@ -19,6 +19,7 @@ export interface IEngine { messages: number; mechanicProcess: number; contractGeneration: number; + achievementsCounter: number; }; decrementAllCounters: (numCycles?: number) => void; checkCounters: () => void; diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index 3153cb2b5..e6aa717c5 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -30,6 +30,7 @@ import { IRouter } from "../ui/Router"; import { WorkerScript } from "../Netscript/WorkerScript"; import { HacknetServer } from "../Hacknet/HacknetServer"; import { ISkillProgress } from "./formulas/skill"; +import { PlayerAchievement } from "../Achievements/Achievements"; export interface IPlayer { // Class members @@ -70,6 +71,7 @@ export interface IPlayer { sleevesFromCovenant: number; sourceFiles: IPlayerOwnedSourceFile[]; exploits: Exploit[]; + achievements: PlayerAchievement[]; lastUpdate: number; totalPlaytime: number; @@ -238,6 +240,7 @@ export interface IPlayer { takeDamage(amt: number): boolean; travel(to: CityName): boolean; giveExploit(exploit: Exploit): void; + giveAchievement(achievementId: string): void; queryStatFromString(str: string): number; getIntelligenceBonus(weight: number): number; getCasinoWinnings(): number; diff --git a/src/PersonObjects/Player/PlayerObject.ts b/src/PersonObjects/Player/PlayerObject.ts index 8497e94a1..07d5bacef 100644 --- a/src/PersonObjects/Player/PlayerObject.ts +++ b/src/PersonObjects/Player/PlayerObject.ts @@ -35,6 +35,7 @@ import { CityName } from "../../Locations/data/CityNames"; import { MoneySourceTracker } from "../../utils/MoneySourceTracker"; import { Reviver, Generic_toJSON, Generic_fromJSON } from "../../utils/JSONReviver"; import { ISkillProgress } from "../formulas/skill"; +import { PlayerAchievement } from '../../Achievements/Achievements'; export class PlayerObject implements IPlayer { // Class members @@ -75,6 +76,7 @@ export class PlayerObject implements IPlayer { sleevesFromCovenant: number; sourceFiles: IPlayerOwnedSourceFile[]; exploits: Exploit[]; + achievements: PlayerAchievement[]; lastUpdate: number; totalPlaytime: number; @@ -243,6 +245,7 @@ export class PlayerObject implements IPlayer { takeDamage: (amt: number) => boolean; travel: (to: CityName) => boolean; giveExploit: (exploit: Exploit) => void; + giveAchievement: (achievementId: string) => void; queryStatFromString: (str: string) => number; getIntelligenceBonus: (weight: number) => number; getCasinoWinnings: () => number; @@ -467,6 +470,7 @@ export class PlayerObject implements IPlayer { this.scriptProdSinceLastAug = 0; this.exploits = []; + this.achievements = []; this.init = generalMethods.init; this.prestigeAugmentation = generalMethods.prestigeAugmentation; @@ -557,6 +561,7 @@ export class PlayerObject implements IPlayer { this.gotoLocation = generalMethods.gotoLocation; this.canAccessResleeving = generalMethods.canAccessResleeving; this.giveExploit = generalMethods.giveExploit; + this.giveAchievement = generalMethods.giveAchievement; this.getIntelligenceBonus = generalMethods.getIntelligenceBonus; this.getCasinoWinnings = generalMethods.getCasinoWinnings; this.hasAugmentation = augmentationMethods.hasAugmentation; diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx index 23ca8ff3e..d4cd65e2e 100644 --- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx @@ -64,6 +64,7 @@ import React from "react"; import { serverMetadata } from "../../Server/data/servers"; import { SnackbarEvents } from "../../ui/React/Snackbar"; import { calculateClassEarnings } from "../formulas/work"; +import { achievements } from "../../Achievements/Achievements"; export function init(this: IPlayer): void { /* Initialize Player's home computer */ @@ -2632,6 +2633,15 @@ export function giveExploit(this: IPlayer, exploit: Exploit): void { } } +export function giveAchievement(this: IPlayer, achievementId: string): void { + const achievement = achievements[achievementId]; + if (!achievement) return; + if (!this.achievements.map(a => a.ID).includes(achievementId)) { + this.achievements.push({ ID: achievementId, unlockedOn: new Date().getTime() }); + SnackbarEvents.emit(`Unlocked Achievement: "${achievement.Name}"`, 'success', 2000); + } +} + export function getIntelligenceBonus(this: IPlayer, weight: number): number { return calculateIntelligenceBonus(this.intelligence, weight); } diff --git a/src/Sidebar/ui/SidebarRoot.tsx b/src/Sidebar/ui/SidebarRoot.tsx index 132758e92..0e51a2175 100644 --- a/src/Sidebar/ui/SidebarRoot.tsx +++ b/src/Sidebar/ui/SidebarRoot.tsx @@ -37,6 +37,7 @@ import CheckIcon from "@mui/icons-material/Check"; // Milestones import HelpIcon from "@mui/icons-material/Help"; // Tutorial import SettingsIcon from "@mui/icons-material/Settings"; // options import DeveloperBoardIcon from "@mui/icons-material/DeveloperBoard"; // Dev +import EmojiEventsIcon from "@mui/icons-material/EmojiEvents"; // Achievements import AccountBoxIcon from "@mui/icons-material/AccountBox"; import PublicIcon from "@mui/icons-material/Public"; import LiveHelpIcon from "@mui/icons-material/LiveHelp"; @@ -256,6 +257,10 @@ export function SidebarRoot(props: IProps): React.ReactElement { props.router.toDevMenu(); } + function clickAchievements(): void { + props.router.toAchievements(); + } + useEffect(() => { // Shortcuts to navigate through the game // Alt-t - Terminal @@ -747,6 +752,21 @@ export function SidebarRoot(props: IProps): React.ReactElement { + + + + + + Achievements + + 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + + case g: + h = (b - r) / d + 2; + break; + + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + + return { + h: h * 100, + s: s * 100, + l: l * 100, + }; + } + + clamp(value) { + if (value > 255) { + value = 255; + } else if (value < 0) { + value = 0; + } + return value; + } +} + +export class Solver { + constructor(target) { + this.target = target; + this.targetHSL = target.hsl(); + this.reusedColor = new Color(0, 0, 0); + } + + solve() { + const result = this.solveNarrow(this.solveWide()); + return { + values: result.values, + loss: result.loss, + filter: this.css(result.values), + }; + } + + solveWide() { + const A = 5; + const c = 15; + const a = [60, 180, 18000, 600, 1.2, 1.2]; + + let best = { loss: Infinity }; + for (let i = 0; best.loss > 25 && i < 3; i++) { + const initial = [50, 20, 3750, 50, 100, 100]; + const result = this.spsa(A, a, c, initial, 1000); + if (result.loss < best.loss) { + best = result; + } + } + return best; + } + + solveNarrow(wide) { + const A = wide.loss; + const c = 2; + const A1 = A + 1; + const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1]; + return this.spsa(A, a, c, wide.values, 500); + } + + spsa(A, a, c, values, iters) { + const alpha = 1; + const gamma = 0.16666666666666666; + + let best = null; + let bestLoss = Infinity; + const deltas = new Array(6); + const highArgs = new Array(6); + const lowArgs = new Array(6); + + for (let k = 0; k < iters; k++) { + const ck = c / Math.pow(k + 1, gamma); + for (let i = 0; i < 6; i++) { + deltas[i] = Math.random() > 0.5 ? 1 : -1; + highArgs[i] = values[i] + ck * deltas[i]; + lowArgs[i] = values[i] - ck * deltas[i]; + } + + const lossDiff = this.loss(highArgs) - this.loss(lowArgs); + for (let i = 0; i < 6; i++) { + const g = lossDiff / (2 * ck) * deltas[i]; + const ak = a[i] / Math.pow(A + k + 1, alpha); + values[i] = fix(values[i] - ak * g, i); + } + + const loss = this.loss(values); + if (loss < bestLoss) { + best = values.slice(0); + bestLoss = loss; + } + } + return { values: best, loss: bestLoss }; + + function fix(value, idx) { + let max = 100; + if (idx === 2 /* saturate */) { + max = 7500; + } else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) { + max = 200; + } + + if (idx === 3 /* hue-rotate */) { + if (value > max) { + value %= max; + } else if (value < 0) { + value = max + value % max; + } + } else if (value < 0) { + value = 0; + } else if (value > max) { + value = max; + } + return value; + } + } + + loss(filters) { + // Argument is array of percentages. + const color = this.reusedColor; + color.set(0, 0, 0); + + color.invert(filters[0] / 100); + color.sepia(filters[1] / 100); + color.saturate(filters[2] / 100); + color.hueRotate(filters[3] * 3.6); + color.brightness(filters[4] / 100); + color.contrast(filters[5] / 100); + + const colorHSL = color.hsl(); + return ( + Math.abs(color.r - this.target.r) + + Math.abs(color.g - this.target.g) + + Math.abs(color.b - this.target.b) + + Math.abs(colorHSL.h - this.targetHSL.h) + + Math.abs(colorHSL.s - this.targetHSL.s) + + Math.abs(colorHSL.l - this.targetHSL.l) + ); + } + + css(filters) { + function fmt(idx, multiplier = 1) { + return Math.round(filters[idx] * multiplier); + } + return `invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(2)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(5)}%)` + } +} + +function hexToRgb(hex): number[] { + // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") + const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, (m, r, g, b) => { + return r + r + g + g + b + b; + }); + + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result + ? [ + parseInt(result[1], 16), + parseInt(result[2], 16), + parseInt(result[3], 16), + ] + : null; +} + +export function getFiltersFromHex(hex): string { + const rgb = hexToRgb(hex); + if (!rgb) return '' + + const [r, g, b] = rgb; + const color = new Color(r, g, b); + const solver = new Solver(color); + return solver.solve().filter; +} diff --git a/src/engine.tsx b/src/engine.tsx index d3afec3b7..4e2c34fa7 100644 --- a/src/engine.tsx +++ b/src/engine.tsx @@ -44,6 +44,7 @@ import { AlertEvents } from "./ui/React/AlertManager"; import { exceptionAlert } from "./utils/helpers/exceptionAlert"; import { startExploits } from "./Exploits/loops"; +import { calculateAchievements } from "./Achievements/Achievements"; import React from "react"; import { setupUncaughtPromiseHandler } from "./UncaughtPromiseHandler"; @@ -65,6 +66,7 @@ const Engine: { messages: number; mechanicProcess: number; contractGeneration: number; + achievementsCounter: number; }; decrementAllCounters: (numCycles?: number) => void; checkCounters: () => void; @@ -163,6 +165,7 @@ const Engine: { messages: 150, mechanicProcess: 5, // Processes certain mechanics (Corporation, Bladeburner) contractGeneration: 3000, // Generate Coding Contracts + achievementsCounter: 300, // Check if we have new achievements }, decrementAllCounters: function (numCycles = 1) { @@ -234,6 +237,11 @@ const Engine: { } Engine.Counters.contractGeneration = 3000; } + + if (Engine.Counters.achievementsCounter <= 0) { + calculateAchievements(); + Engine.Counters.achievementsCounter = 300; + } }, load: function (saveString) { diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index 4c37690a7..f9966ff0d 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -76,6 +76,7 @@ import { InvitationModal } from "../Faction/ui/InvitationModal"; import { enterBitNode } from "../RedPill"; import { Context } from "./Context"; import { RecoveryMode, RecoveryRoot } from "./React/RecoveryRoot"; +import { AchievementsRoot } from "../Achievements/AchievementsRoot"; const htmlLocation = location; @@ -183,6 +184,9 @@ export let Router: IRouter = { toStaneksGift: () => { throw new Error("Router called before initialization"); }, + toAchievements: () => { + throw new Error("Router called before initialization"); + } }; function determineStartPage(player: IPlayer): Page { @@ -287,6 +291,9 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme toStaneksGift: () => { setPage(Page.StaneksGift); }, + toAchievements: () => { + setPage(Page.Achievements); + }, }; useEffect(() => { @@ -408,7 +415,9 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme Router.toTerminal(); }} /> - ) : ( + ) : page === Page.Achievements ? ( + + ) : ( <> Cannot load diff --git a/src/ui/Router.ts b/src/ui/Router.ts index 6b09983bb..4056a44ac 100644 --- a/src/ui/Router.ts +++ b/src/ui/Router.ts @@ -36,6 +36,7 @@ export enum Page { Loading, StaneksGift, Recovery, + Achievements, } export interface ScriptEditorRouteOptions { @@ -80,4 +81,5 @@ export interface IRouter { toBladeburnerCinematic(): void; toLocation(location: Location): void; toStaneksGift(): void; + toAchievements(): void; } diff --git a/tools/README.md b/tools/README.md index f29ebd87e..0f2e5f759 100644 --- a/tools/README.md +++ b/tools/README.md @@ -10,3 +10,13 @@ It decodes the save and prettifies the output. Canno be used to modify a save ga ```sh node ./pretty-save.js 'C:\\Users\\martin\\Desktop\\bitburnerSave_1641395736_BN12x14.json' 'C:\\Users\\martin\\Desktop\\pretty.json' ``` + +## Fetch Steam Achievements Data + +Used to synchronize the achievements info in steamworks to the game's data.json + +**Usage** +```sh +# Get your key here: https://steamcommunity.com/dev/apikey +node fetch-steam-achievements-data.js DEVKEYDEVKEYDEVKEYDEVKEY +``` diff --git a/tools/fetch-steam-achievements-data.js b/tools/fetch-steam-achievements-data.js new file mode 100644 index 000000000..aef79c8c3 --- /dev/null +++ b/tools/fetch-steam-achievements-data.js @@ -0,0 +1,69 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const https = require('https') +const fs = require('fs').promises; +const path = require('path'); + +const key = process.argv[2] + +function getRawJSON() { + return new Promise((resolve, reject) => { + const options = { + hostname: 'api.steampowered.com', + port: 443, + path: `/ISteamUserStats/GetSchemaForGame/v0002/?appid=1812820&key=${key}`, + method: 'GET', + headers: { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + } + } + + let data = []; + const req = https.request(options, res => { + console.log(`statusCode: ${res.statusCode}`) + + res.on('data', chunk => { + data.push(chunk) + }) + + res.on('end', () => { + console.log('Response ended: '); + resolve(Buffer.concat(data).toString()); + }); + }) + + req.on('error', error => { + console.error(error) + req.end(); + reject(error); + }); + + req.end(); + }); +} + +async function fetchAchievementsData() { + const raw = await getRawJSON(); + const o = JSON.parse(raw); + const achievements = {}; + o.game.availableGameStats.achievements.forEach((a) => { + achievements[a.name] = { + ID: a.name, + Name: a.displayName, + Description: a.description, + }; + }) + + const data = { + note: '***** Generated from a script, overwritten by steam achievements data *****', + fetchedOn: new Date().getTime(), + achievements, + } + + const jsonPath = path.resolve(__dirname, '../src/Achievements/AchievementData.json'); + await fs.writeFile(jsonPath, JSON.stringify(data, null, 2)); + return data; +} + +fetchAchievementsData(). + then((json) => console.log(JSON.stringify(json, null, 2))); diff --git a/tsconfig.json b/tsconfig.json index fcf8643fb..3cfda7488 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,8 @@ "target": "es6", "sourceMap": true, "strict": true, - "types": ["cypress", "@testing-library/cypress", "node", "raw-loader.d.ts"] + "resolveJsonModule": true, + "types": ["cypress", "@testing-library/cypress", "node"] }, "exclude": ["node_modules"] } diff --git a/webpack.config.js b/webpack.config.js index f9203a87f..2b72bb10e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -142,7 +142,7 @@ module.exports = (env, argv) => { module: { rules: [ { - test: /\.(js|jsx|ts|tsx)$/, + test: /\.(js$|jsx|ts|tsx)$/, exclude: /node_modules/, use: { loader: "babel-loader", From 38e10e193e011546e61d02a8732b35dd34444381 Mon Sep 17 00:00:00 2001 From: Martin Fournier Date: Sat, 8 Jan 2022 05:45:00 -0500 Subject: [PATCH 2/3] Add achievements dev menu --- src/DevMenu.tsx | 2 + src/DevMenu/ui/Achievements.tsx | 109 ++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 src/DevMenu/ui/Achievements.tsx diff --git a/src/DevMenu.tsx b/src/DevMenu.tsx index 856e44f9c..62300a430 100644 --- a/src/DevMenu.tsx +++ b/src/DevMenu.tsx @@ -22,6 +22,7 @@ import { StockMarket } from "./DevMenu/ui/StockMarket"; import { Sleeves } from "./DevMenu/ui/Sleeves"; import { Stanek } from "./DevMenu/ui/Stanek"; import { TimeSkip } from "./DevMenu/ui/TimeSkip"; +import { Achievements } from "./DevMenu/ui/Achievements"; import Typography from "@mui/material/Typography"; import { Exploit } from "./Exploits/Exploit"; @@ -61,6 +62,7 @@ export function DevMenuRoot(props: IProps): React.ReactElement { {props.player.augmentations.some((aug) => aug.name === AugmentationNames.StaneksGift1) && } + ); } diff --git a/src/DevMenu/ui/Achievements.tsx b/src/DevMenu/ui/Achievements.tsx new file mode 100644 index 000000000..2d257dcd3 --- /dev/null +++ b/src/DevMenu/ui/Achievements.tsx @@ -0,0 +1,109 @@ +import React, { useState } from "react"; + +import Accordion from "@mui/material/Accordion"; +import AccordionSummary from "@mui/material/AccordionSummary"; +import AccordionDetails from "@mui/material/AccordionDetails"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import Typography from "@mui/material/Typography"; +import Button from "@mui/material/Button"; +import ButtonGroup from "@mui/material/ButtonGroup"; +import { Tooltip } from "@mui/material"; +import LockIcon from '@mui/icons-material/Lock'; +import LockOpenIcon from '@mui/icons-material/LockOpen'; + +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { achievements } from "../../Achievements/Achievements"; +import { IEngine } from "../../IEngine"; + +interface IProps { + player: IPlayer; + engine: IEngine; +} + +export function Achievements(props: IProps): React.ReactElement { + const [playerAchievement, setPlayerAchievements] = useState(props.player.achievements.map(m => m.ID)); + + function grantAchievement(id: string): void { + props.player.giveAchievement(id); + setPlayerAchievements(props.player.achievements.map(m => m.ID)); + } + + function grantAllAchievements(): void { + Object.values(achievements).forEach(a => props.player.giveAchievement(a.ID)); + setPlayerAchievements(props.player.achievements.map(m => m.ID)); + } + + function removeAchievement(id: string): void { + props.player.achievements = props.player.achievements.filter(a => a.ID !== id); + setPlayerAchievements(props.player.achievements.map(m => m.ID)); + } + + function clearAchievements(): void { + props.player.achievements = []; + setPlayerAchievements(props.player.achievements.map(m => m.ID)); + } + + function disableEngine(): void { + props.engine.Counters.achievementsCounter = Number.MAX_VALUE; + } + + function enableEngine(): void { + props.engine.Counters.achievementsCounter = 0 + } + + return ( + + }> + Achievements + + + + + + + + + + {Object.values(achievements).map((i) => { + const achieved = playerAchievement.includes(i.ID); + return + + + + + })} + +
+ + Achievements: + + + + + + + +
+ {achieved ? ( + + + + ) : ( + + + + )} + + {i.ID}
{i.Description}}> + {i.Name}: +
+
+ + + + +
+
+
+ ); +} From 4f42c920b07e8dd27a5bc3f13c7321265551557e Mon Sep 17 00:00:00 2001 From: Martin Fournier Date: Thu, 6 Jan 2022 13:43:13 -0500 Subject: [PATCH 3/3] Add grayscale achievement SVG icons in /dist/ They are generated using the script in the steam achievement folder. --- dist/icons/achievements/$1Q.svg | 70 +++++++++++++++++++ dist/icons/achievements/-1b.svg | 70 +++++++++++++++++++ dist/icons/achievements/1H.svg | 70 +++++++++++++++++++ dist/icons/achievements/2DAYS.svg | 70 +++++++++++++++++++ dist/icons/achievements/4S.svg | 70 +++++++++++++++++++ dist/icons/achievements/BLADE.svg | 70 +++++++++++++++++++ dist/icons/achievements/BLADE100K.svg | 75 ++++++++++++++++++++ dist/icons/achievements/BLADEOVERCLOCK.svg | 75 ++++++++++++++++++++ dist/icons/achievements/BN1+.svg | 70 +++++++++++++++++++ dist/icons/achievements/BN10+.svg | 70 +++++++++++++++++++ dist/icons/achievements/BN12+.svg | 70 +++++++++++++++++++ dist/icons/achievements/BN13+.svg | 70 +++++++++++++++++++ dist/icons/achievements/BN2+.svg | 70 +++++++++++++++++++ dist/icons/achievements/BN3+.svg | 70 +++++++++++++++++++ dist/icons/achievements/BN6+.svg | 70 +++++++++++++++++++ dist/icons/achievements/BN7+.svg | 70 +++++++++++++++++++ dist/icons/achievements/BN8+.svg | 70 +++++++++++++++++++ dist/icons/achievements/BN9+.svg | 70 +++++++++++++++++++ dist/icons/achievements/CORP.svg | 70 +++++++++++++++++++ dist/icons/achievements/CORP1000.svg | 75 ++++++++++++++++++++ dist/icons/achievements/CORPCITY.svg | 75 ++++++++++++++++++++ dist/icons/achievements/CORPLOBBY.svg | 75 ++++++++++++++++++++ dist/icons/achievements/CORPRE.svg | 75 ++++++++++++++++++++ dist/icons/achievements/CSEC.svg | 71 +++++++++++++++++++ dist/icons/achievements/GANG.svg | 70 +++++++++++++++++++ dist/icons/achievements/GANG100%.svg | 75 ++++++++++++++++++++ dist/icons/achievements/GANG10000.svg | 75 ++++++++++++++++++++ dist/icons/achievements/GANGMAX.svg | 75 ++++++++++++++++++++ dist/icons/achievements/HASHNET.svg | 70 +++++++++++++++++++ dist/icons/achievements/HASHNETALL.svg | 75 ++++++++++++++++++++ dist/icons/achievements/HASHNETCAP.svg | 75 ++++++++++++++++++++ dist/icons/achievements/HASHNETMAX.svg | 75 ++++++++++++++++++++ dist/icons/achievements/HASHNETMONEY.svg | 75 ++++++++++++++++++++ dist/icons/achievements/INT255.svg | 75 ++++++++++++++++++++ dist/icons/achievements/NiteSec.svg | 71 +++++++++++++++++++ dist/icons/achievements/OUCH.svg | 70 +++++++++++++++++++ dist/icons/achievements/SF-1.svg | 71 +++++++++++++++++++ dist/icons/achievements/SF1.1.svg | 70 +++++++++++++++++++ dist/icons/achievements/SF10.1.svg | 71 +++++++++++++++++++ dist/icons/achievements/SF11.1.svg | 71 +++++++++++++++++++ dist/icons/achievements/SF12.1.svg | 71 +++++++++++++++++++ dist/icons/achievements/SF2.1.svg | 70 +++++++++++++++++++ dist/icons/achievements/SF3.1.svg | 70 +++++++++++++++++++ dist/icons/achievements/SF4.1.svg | 70 +++++++++++++++++++ dist/icons/achievements/SF5.1.svg | 70 +++++++++++++++++++ dist/icons/achievements/SF6.1.svg | 70 +++++++++++++++++++ dist/icons/achievements/SF7.1.svg | 70 +++++++++++++++++++ dist/icons/achievements/SF8.1.svg | 70 +++++++++++++++++++ dist/icons/achievements/SF9.1.svg | 70 +++++++++++++++++++ dist/icons/achievements/SLEEVE8.svg | 70 +++++++++++++++++++ dist/icons/achievements/TBH.svg | 81 ++++++++++++++++++++++ dist/icons/achievements/TOR.svg | 70 +++++++++++++++++++ dist/icons/achievements/TRAVEL.svg | 70 +++++++++++++++++++ dist/icons/achievements/WORKOUT.svg | 70 +++++++++++++++++++ dist/icons/achievements/bigcost.svg | 70 +++++++++++++++++++ dist/icons/achievements/bitrunners.svg | 70 +++++++++++++++++++ dist/icons/achievements/broker.svg | 70 +++++++++++++++++++ dist/icons/achievements/combat3000.svg | 75 ++++++++++++++++++++ dist/icons/achievements/daedalus.svg | 70 +++++++++++++++++++ dist/icons/achievements/discount.svg | 70 +++++++++++++++++++ dist/icons/achievements/donation.svg | 70 +++++++++++++++++++ dist/icons/achievements/drain.svg | 70 +++++++++++++++++++ dist/icons/achievements/folders.svg | 70 +++++++++++++++++++ dist/icons/achievements/formulas.svg | 70 +++++++++++++++++++ dist/icons/achievements/forze.svg | 70 +++++++++++++++++++ dist/icons/achievements/hack100000.svg | 75 ++++++++++++++++++++ dist/icons/achievements/hacknet-10m.svg | 75 ++++++++++++++++++++ dist/icons/achievements/hacknet-all.svg | 75 ++++++++++++++++++++ dist/icons/achievements/hacknet-max.svg | 75 ++++++++++++++++++++ dist/icons/achievements/illuminati.svg | 70 +++++++++++++++++++ dist/icons/achievements/install.svg | 70 +++++++++++++++++++ dist/icons/achievements/install_100.svg | 75 ++++++++++++++++++++ dist/icons/achievements/karma.svg | 70 +++++++++++++++++++ dist/icons/achievements/maxcores.svg | 70 +++++++++++++++++++ dist/icons/achievements/maxram.svg | 70 +++++++++++++++++++ dist/icons/achievements/nf255.svg | 70 +++++++++++++++++++ dist/icons/achievements/node.svg | 70 +++++++++++++++++++ dist/icons/achievements/ns2.svg | 70 +++++++++++++++++++ dist/icons/achievements/p0.svg | 70 +++++++++++++++++++ dist/icons/achievements/p1.svg | 70 +++++++++++++++++++ dist/icons/achievements/p2.svg | 70 +++++++++++++++++++ dist/icons/achievements/p3.svg | 70 +++++++++++++++++++ dist/icons/achievements/p4.svg | 70 +++++++++++++++++++ dist/icons/achievements/queue40.svg | 75 ++++++++++++++++++++ dist/icons/achievements/reputation.svg | 75 ++++++++++++++++++++ dist/icons/achievements/run1000.svg | 75 ++++++++++++++++++++ dist/icons/achievements/thecovenant.svg | 70 +++++++++++++++++++ 87 files changed, 6222 insertions(+) create mode 100644 dist/icons/achievements/$1Q.svg create mode 100644 dist/icons/achievements/-1b.svg create mode 100644 dist/icons/achievements/1H.svg create mode 100644 dist/icons/achievements/2DAYS.svg create mode 100644 dist/icons/achievements/4S.svg create mode 100644 dist/icons/achievements/BLADE.svg create mode 100644 dist/icons/achievements/BLADE100K.svg create mode 100644 dist/icons/achievements/BLADEOVERCLOCK.svg create mode 100644 dist/icons/achievements/BN1+.svg create mode 100644 dist/icons/achievements/BN10+.svg create mode 100644 dist/icons/achievements/BN12+.svg create mode 100644 dist/icons/achievements/BN13+.svg create mode 100644 dist/icons/achievements/BN2+.svg create mode 100644 dist/icons/achievements/BN3+.svg create mode 100644 dist/icons/achievements/BN6+.svg create mode 100644 dist/icons/achievements/BN7+.svg create mode 100644 dist/icons/achievements/BN8+.svg create mode 100644 dist/icons/achievements/BN9+.svg create mode 100644 dist/icons/achievements/CORP.svg create mode 100644 dist/icons/achievements/CORP1000.svg create mode 100644 dist/icons/achievements/CORPCITY.svg create mode 100644 dist/icons/achievements/CORPLOBBY.svg create mode 100644 dist/icons/achievements/CORPRE.svg create mode 100644 dist/icons/achievements/CSEC.svg create mode 100644 dist/icons/achievements/GANG.svg create mode 100644 dist/icons/achievements/GANG100%.svg create mode 100644 dist/icons/achievements/GANG10000.svg create mode 100644 dist/icons/achievements/GANGMAX.svg create mode 100644 dist/icons/achievements/HASHNET.svg create mode 100644 dist/icons/achievements/HASHNETALL.svg create mode 100644 dist/icons/achievements/HASHNETCAP.svg create mode 100644 dist/icons/achievements/HASHNETMAX.svg create mode 100644 dist/icons/achievements/HASHNETMONEY.svg create mode 100644 dist/icons/achievements/INT255.svg create mode 100644 dist/icons/achievements/NiteSec.svg create mode 100644 dist/icons/achievements/OUCH.svg create mode 100644 dist/icons/achievements/SF-1.svg create mode 100644 dist/icons/achievements/SF1.1.svg create mode 100644 dist/icons/achievements/SF10.1.svg create mode 100644 dist/icons/achievements/SF11.1.svg create mode 100644 dist/icons/achievements/SF12.1.svg create mode 100644 dist/icons/achievements/SF2.1.svg create mode 100644 dist/icons/achievements/SF3.1.svg create mode 100644 dist/icons/achievements/SF4.1.svg create mode 100644 dist/icons/achievements/SF5.1.svg create mode 100644 dist/icons/achievements/SF6.1.svg create mode 100644 dist/icons/achievements/SF7.1.svg create mode 100644 dist/icons/achievements/SF8.1.svg create mode 100644 dist/icons/achievements/SF9.1.svg create mode 100644 dist/icons/achievements/SLEEVE8.svg create mode 100644 dist/icons/achievements/TBH.svg create mode 100644 dist/icons/achievements/TOR.svg create mode 100644 dist/icons/achievements/TRAVEL.svg create mode 100644 dist/icons/achievements/WORKOUT.svg create mode 100644 dist/icons/achievements/bigcost.svg create mode 100644 dist/icons/achievements/bitrunners.svg create mode 100644 dist/icons/achievements/broker.svg create mode 100644 dist/icons/achievements/combat3000.svg create mode 100644 dist/icons/achievements/daedalus.svg create mode 100644 dist/icons/achievements/discount.svg create mode 100644 dist/icons/achievements/donation.svg create mode 100644 dist/icons/achievements/drain.svg create mode 100644 dist/icons/achievements/folders.svg create mode 100644 dist/icons/achievements/formulas.svg create mode 100644 dist/icons/achievements/forze.svg create mode 100644 dist/icons/achievements/hack100000.svg create mode 100644 dist/icons/achievements/hacknet-10m.svg create mode 100644 dist/icons/achievements/hacknet-all.svg create mode 100644 dist/icons/achievements/hacknet-max.svg create mode 100644 dist/icons/achievements/illuminati.svg create mode 100644 dist/icons/achievements/install.svg create mode 100644 dist/icons/achievements/install_100.svg create mode 100644 dist/icons/achievements/karma.svg create mode 100644 dist/icons/achievements/maxcores.svg create mode 100644 dist/icons/achievements/maxram.svg create mode 100644 dist/icons/achievements/nf255.svg create mode 100644 dist/icons/achievements/node.svg create mode 100644 dist/icons/achievements/ns2.svg create mode 100644 dist/icons/achievements/p0.svg create mode 100644 dist/icons/achievements/p1.svg create mode 100644 dist/icons/achievements/p2.svg create mode 100644 dist/icons/achievements/p3.svg create mode 100644 dist/icons/achievements/p4.svg create mode 100644 dist/icons/achievements/queue40.svg create mode 100644 dist/icons/achievements/reputation.svg create mode 100644 dist/icons/achievements/run1000.svg create mode 100644 dist/icons/achievements/thecovenant.svg diff --git a/dist/icons/achievements/$1Q.svg b/dist/icons/achievements/$1Q.svg new file mode 100644 index 000000000..4b51b63bd --- /dev/null +++ b/dist/icons/achievements/$1Q.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + $1Q + + diff --git a/dist/icons/achievements/-1b.svg b/dist/icons/achievements/-1b.svg new file mode 100644 index 000000000..d582e1fb2 --- /dev/null +++ b/dist/icons/achievements/-1b.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + -$1b + + diff --git a/dist/icons/achievements/1H.svg b/dist/icons/achievements/1H.svg new file mode 100644 index 000000000..8bc9b30df --- /dev/null +++ b/dist/icons/achievements/1H.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + 1H + + diff --git a/dist/icons/achievements/2DAYS.svg b/dist/icons/achievements/2DAYS.svg new file mode 100644 index 000000000..34aecf3d0 --- /dev/null +++ b/dist/icons/achievements/2DAYS.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + 2 DAYS + + diff --git a/dist/icons/achievements/4S.svg b/dist/icons/achievements/4S.svg new file mode 100644 index 000000000..efcca024f --- /dev/null +++ b/dist/icons/achievements/4S.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + 4S + + diff --git a/dist/icons/achievements/BLADE.svg b/dist/icons/achievements/BLADE.svg new file mode 100644 index 000000000..1e1a47a43 --- /dev/null +++ b/dist/icons/achievements/BLADE.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + BLADE + + diff --git a/dist/icons/achievements/BLADE100K.svg b/dist/icons/achievements/BLADE100K.svg new file mode 100644 index 000000000..ed8ca6170 --- /dev/null +++ b/dist/icons/achievements/BLADE100K.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + BLADE100000 + + diff --git a/dist/icons/achievements/BLADEOVERCLOCK.svg b/dist/icons/achievements/BLADEOVERCLOCK.svg new file mode 100644 index 000000000..a35fc796a --- /dev/null +++ b/dist/icons/achievements/BLADEOVERCLOCK.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + BLADEOC + + diff --git a/dist/icons/achievements/BN1+.svg b/dist/icons/achievements/BN1+.svg new file mode 100644 index 000000000..66eae0b45 --- /dev/null +++ b/dist/icons/achievements/BN1+.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + BN1+ + + diff --git a/dist/icons/achievements/BN10+.svg b/dist/icons/achievements/BN10+.svg new file mode 100644 index 000000000..c4d6847cd --- /dev/null +++ b/dist/icons/achievements/BN10+.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + BN10+ + + diff --git a/dist/icons/achievements/BN12+.svg b/dist/icons/achievements/BN12+.svg new file mode 100644 index 000000000..8ec06bf4a --- /dev/null +++ b/dist/icons/achievements/BN12+.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + BN12+ + + diff --git a/dist/icons/achievements/BN13+.svg b/dist/icons/achievements/BN13+.svg new file mode 100644 index 000000000..c5b043cc3 --- /dev/null +++ b/dist/icons/achievements/BN13+.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + BN13+ + + diff --git a/dist/icons/achievements/BN2+.svg b/dist/icons/achievements/BN2+.svg new file mode 100644 index 000000000..80e8fdffc --- /dev/null +++ b/dist/icons/achievements/BN2+.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + BN2+ + + diff --git a/dist/icons/achievements/BN3+.svg b/dist/icons/achievements/BN3+.svg new file mode 100644 index 000000000..3a6598836 --- /dev/null +++ b/dist/icons/achievements/BN3+.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + BN3+ + + diff --git a/dist/icons/achievements/BN6+.svg b/dist/icons/achievements/BN6+.svg new file mode 100644 index 000000000..c8ea68153 --- /dev/null +++ b/dist/icons/achievements/BN6+.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + BN6+ + + diff --git a/dist/icons/achievements/BN7+.svg b/dist/icons/achievements/BN7+.svg new file mode 100644 index 000000000..93ee9d9c1 --- /dev/null +++ b/dist/icons/achievements/BN7+.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + BN7+ + + diff --git a/dist/icons/achievements/BN8+.svg b/dist/icons/achievements/BN8+.svg new file mode 100644 index 000000000..fd1926b8b --- /dev/null +++ b/dist/icons/achievements/BN8+.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + BN8+ + + diff --git a/dist/icons/achievements/BN9+.svg b/dist/icons/achievements/BN9+.svg new file mode 100644 index 000000000..ed1273ada --- /dev/null +++ b/dist/icons/achievements/BN9+.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + BN9+ + + diff --git a/dist/icons/achievements/CORP.svg b/dist/icons/achievements/CORP.svg new file mode 100644 index 000000000..d085570b8 --- /dev/null +++ b/dist/icons/achievements/CORP.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + CORP + + diff --git a/dist/icons/achievements/CORP1000.svg b/dist/icons/achievements/CORP1000.svg new file mode 100644 index 000000000..0c6a287da --- /dev/null +++ b/dist/icons/achievements/CORP1000.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + CORP1000 + + diff --git a/dist/icons/achievements/CORPCITY.svg b/dist/icons/achievements/CORPCITY.svg new file mode 100644 index 000000000..a51577a6f --- /dev/null +++ b/dist/icons/achievements/CORPCITY.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + CORP3000 + + diff --git a/dist/icons/achievements/CORPLOBBY.svg b/dist/icons/achievements/CORPLOBBY.svg new file mode 100644 index 000000000..b08e951cf --- /dev/null +++ b/dist/icons/achievements/CORPLOBBY.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + CORPLOBBY + + diff --git a/dist/icons/achievements/CORPRE.svg b/dist/icons/achievements/CORPRE.svg new file mode 100644 index 000000000..945ad9295 --- /dev/null +++ b/dist/icons/achievements/CORPRE.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + CORPRE + + diff --git a/dist/icons/achievements/CSEC.svg b/dist/icons/achievements/CSEC.svg new file mode 100644 index 000000000..493d2e937 --- /dev/null +++ b/dist/icons/achievements/CSEC.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + CSEC + + diff --git a/dist/icons/achievements/GANG.svg b/dist/icons/achievements/GANG.svg new file mode 100644 index 000000000..94c1188fb --- /dev/null +++ b/dist/icons/achievements/GANG.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + GANG + + diff --git a/dist/icons/achievements/GANG100%.svg b/dist/icons/achievements/GANG100%.svg new file mode 100644 index 000000000..ecbef464c --- /dev/null +++ b/dist/icons/achievements/GANG100%.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + GANG100% + + diff --git a/dist/icons/achievements/GANG10000.svg b/dist/icons/achievements/GANG10000.svg new file mode 100644 index 000000000..2a149da66 --- /dev/null +++ b/dist/icons/achievements/GANG10000.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + GANG10000 + + diff --git a/dist/icons/achievements/GANGMAX.svg b/dist/icons/achievements/GANGMAX.svg new file mode 100644 index 000000000..f755f92fd --- /dev/null +++ b/dist/icons/achievements/GANGMAX.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + GANGMAX + + diff --git a/dist/icons/achievements/HASHNET.svg b/dist/icons/achievements/HASHNET.svg new file mode 100644 index 000000000..70abf6eed --- /dev/null +++ b/dist/icons/achievements/HASHNET.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + HASHNET + + diff --git a/dist/icons/achievements/HASHNETALL.svg b/dist/icons/achievements/HASHNETALL.svg new file mode 100644 index 000000000..cd2f6be76 --- /dev/null +++ b/dist/icons/achievements/HASHNETALL.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + HASHNETALL + + diff --git a/dist/icons/achievements/HASHNETCAP.svg b/dist/icons/achievements/HASHNETCAP.svg new file mode 100644 index 000000000..648778096 --- /dev/null +++ b/dist/icons/achievements/HASHNETCAP.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + HASHNET100% + + diff --git a/dist/icons/achievements/HASHNETMAX.svg b/dist/icons/achievements/HASHNETMAX.svg new file mode 100644 index 000000000..e7b40e803 --- /dev/null +++ b/dist/icons/achievements/HASHNETMAX.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + HASHNETMAX + + diff --git a/dist/icons/achievements/HASHNETMONEY.svg b/dist/icons/achievements/HASHNETMONEY.svg new file mode 100644 index 000000000..2ed64dfc2 --- /dev/null +++ b/dist/icons/achievements/HASHNETMONEY.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + HASHNET$1B + + diff --git a/dist/icons/achievements/INT255.svg b/dist/icons/achievements/INT255.svg new file mode 100644 index 000000000..e578553cf --- /dev/null +++ b/dist/icons/achievements/INT255.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + INT255 + + diff --git a/dist/icons/achievements/NiteSec.svg b/dist/icons/achievements/NiteSec.svg new file mode 100644 index 000000000..60168f42f --- /dev/null +++ b/dist/icons/achievements/NiteSec.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + NiteSec + + diff --git a/dist/icons/achievements/OUCH.svg b/dist/icons/achievements/OUCH.svg new file mode 100644 index 000000000..3ae7e7223 --- /dev/null +++ b/dist/icons/achievements/OUCH.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + OUCH! + + diff --git a/dist/icons/achievements/SF-1.svg b/dist/icons/achievements/SF-1.svg new file mode 100644 index 000000000..01bc07937 --- /dev/null +++ b/dist/icons/achievements/SF-1.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + SF -1 + + diff --git a/dist/icons/achievements/SF1.1.svg b/dist/icons/achievements/SF1.1.svg new file mode 100644 index 000000000..5ae7bf73c --- /dev/null +++ b/dist/icons/achievements/SF1.1.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + SF1.1 + + diff --git a/dist/icons/achievements/SF10.1.svg b/dist/icons/achievements/SF10.1.svg new file mode 100644 index 000000000..b387d1c22 --- /dev/null +++ b/dist/icons/achievements/SF10.1.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + SF10.1 + + diff --git a/dist/icons/achievements/SF11.1.svg b/dist/icons/achievements/SF11.1.svg new file mode 100644 index 000000000..fbf5a5653 --- /dev/null +++ b/dist/icons/achievements/SF11.1.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + SF11.1 + + diff --git a/dist/icons/achievements/SF12.1.svg b/dist/icons/achievements/SF12.1.svg new file mode 100644 index 000000000..c8a72d4b5 --- /dev/null +++ b/dist/icons/achievements/SF12.1.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + SF12.1 + + diff --git a/dist/icons/achievements/SF2.1.svg b/dist/icons/achievements/SF2.1.svg new file mode 100644 index 000000000..c7edf44cd --- /dev/null +++ b/dist/icons/achievements/SF2.1.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + SF2.1 + + diff --git a/dist/icons/achievements/SF3.1.svg b/dist/icons/achievements/SF3.1.svg new file mode 100644 index 000000000..58c635a52 --- /dev/null +++ b/dist/icons/achievements/SF3.1.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + SF3.1 + + diff --git a/dist/icons/achievements/SF4.1.svg b/dist/icons/achievements/SF4.1.svg new file mode 100644 index 000000000..58fc523a3 --- /dev/null +++ b/dist/icons/achievements/SF4.1.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + SF4.1 + + diff --git a/dist/icons/achievements/SF5.1.svg b/dist/icons/achievements/SF5.1.svg new file mode 100644 index 000000000..6033895de --- /dev/null +++ b/dist/icons/achievements/SF5.1.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + SF5.1 + + diff --git a/dist/icons/achievements/SF6.1.svg b/dist/icons/achievements/SF6.1.svg new file mode 100644 index 000000000..ecbfdfcd0 --- /dev/null +++ b/dist/icons/achievements/SF6.1.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + SF6.1 + + diff --git a/dist/icons/achievements/SF7.1.svg b/dist/icons/achievements/SF7.1.svg new file mode 100644 index 000000000..44c8b6430 --- /dev/null +++ b/dist/icons/achievements/SF7.1.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + SF7.1 + + diff --git a/dist/icons/achievements/SF8.1.svg b/dist/icons/achievements/SF8.1.svg new file mode 100644 index 000000000..4c44d9354 --- /dev/null +++ b/dist/icons/achievements/SF8.1.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + SF8.1 + + diff --git a/dist/icons/achievements/SF9.1.svg b/dist/icons/achievements/SF9.1.svg new file mode 100644 index 000000000..21f1c4344 --- /dev/null +++ b/dist/icons/achievements/SF9.1.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + SF9.1 + + diff --git a/dist/icons/achievements/SLEEVE8.svg b/dist/icons/achievements/SLEEVE8.svg new file mode 100644 index 000000000..0db368e18 --- /dev/null +++ b/dist/icons/achievements/SLEEVE8.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + 8 SLEEVE + + diff --git a/dist/icons/achievements/TBH.svg b/dist/icons/achievements/TBH.svg new file mode 100644 index 000000000..2153ac84e --- /dev/null +++ b/dist/icons/achievements/TBH.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + Theblackhand + + diff --git a/dist/icons/achievements/TOR.svg b/dist/icons/achievements/TOR.svg new file mode 100644 index 000000000..430090bb5 --- /dev/null +++ b/dist/icons/achievements/TOR.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + TOR + + diff --git a/dist/icons/achievements/TRAVEL.svg b/dist/icons/achievements/TRAVEL.svg new file mode 100644 index 000000000..ef059574b --- /dev/null +++ b/dist/icons/achievements/TRAVEL.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + WORLD + + diff --git a/dist/icons/achievements/WORKOUT.svg b/dist/icons/achievements/WORKOUT.svg new file mode 100644 index 000000000..62befc520 --- /dev/null +++ b/dist/icons/achievements/WORKOUT.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + WORKOUT + + diff --git a/dist/icons/achievements/bigcost.svg b/dist/icons/achievements/bigcost.svg new file mode 100644 index 000000000..e6596813e --- /dev/null +++ b/dist/icons/achievements/bigcost.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + 32GB+ + + diff --git a/dist/icons/achievements/bitrunners.svg b/dist/icons/achievements/bitrunners.svg new file mode 100644 index 000000000..a03ba8ece --- /dev/null +++ b/dist/icons/achievements/bitrunners.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + BitRunners + + diff --git a/dist/icons/achievements/broker.svg b/dist/icons/achievements/broker.svg new file mode 100644 index 000000000..a1a2e2d01 --- /dev/null +++ b/dist/icons/achievements/broker.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + $1q + + diff --git a/dist/icons/achievements/combat3000.svg b/dist/icons/achievements/combat3000.svg new file mode 100644 index 000000000..683a90947 --- /dev/null +++ b/dist/icons/achievements/combat3000.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + combat3000 + + diff --git a/dist/icons/achievements/daedalus.svg b/dist/icons/achievements/daedalus.svg new file mode 100644 index 000000000..dcf7a640d --- /dev/null +++ b/dist/icons/achievements/daedalus.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + Daedalus + + diff --git a/dist/icons/achievements/discount.svg b/dist/icons/achievements/discount.svg new file mode 100644 index 000000000..f7ef0c238 --- /dev/null +++ b/dist/icons/achievements/discount.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + -10% + + diff --git a/dist/icons/achievements/donation.svg b/dist/icons/achievements/donation.svg new file mode 100644 index 000000000..da3c651a8 --- /dev/null +++ b/dist/icons/achievements/donation.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + DONATION + + diff --git a/dist/icons/achievements/drain.svg b/dist/icons/achievements/drain.svg new file mode 100644 index 000000000..1b5a4b005 --- /dev/null +++ b/dist/icons/achievements/drain.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + $0 + + diff --git a/dist/icons/achievements/folders.svg b/dist/icons/achievements/folders.svg new file mode 100644 index 000000000..080057bfd --- /dev/null +++ b/dist/icons/achievements/folders.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + /scripts/ + + diff --git a/dist/icons/achievements/formulas.svg b/dist/icons/achievements/formulas.svg new file mode 100644 index 000000000..6c5be3e90 --- /dev/null +++ b/dist/icons/achievements/formulas.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + f(x) + + diff --git a/dist/icons/achievements/forze.svg b/dist/icons/achievements/forze.svg new file mode 100644 index 000000000..a8b57f44e --- /dev/null +++ b/dist/icons/achievements/forze.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + FROZEN + + diff --git a/dist/icons/achievements/hack100000.svg b/dist/icons/achievements/hack100000.svg new file mode 100644 index 000000000..698e8e518 --- /dev/null +++ b/dist/icons/achievements/hack100000.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + hack100000 + + diff --git a/dist/icons/achievements/hacknet-10m.svg b/dist/icons/achievements/hacknet-10m.svg new file mode 100644 index 000000000..4b3c9e419 --- /dev/null +++ b/dist/icons/achievements/hacknet-10m.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + HACKNET$10m + + diff --git a/dist/icons/achievements/hacknet-all.svg b/dist/icons/achievements/hacknet-all.svg new file mode 100644 index 000000000..dcd469710 --- /dev/null +++ b/dist/icons/achievements/hacknet-all.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + HACKNETALL + + diff --git a/dist/icons/achievements/hacknet-max.svg b/dist/icons/achievements/hacknet-max.svg new file mode 100644 index 000000000..8fe597241 --- /dev/null +++ b/dist/icons/achievements/hacknet-max.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + HACKNETMAX + + diff --git a/dist/icons/achievements/illuminati.svg b/dist/icons/achievements/illuminati.svg new file mode 100644 index 000000000..526367451 --- /dev/null +++ b/dist/icons/achievements/illuminati.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + Illuminati + + diff --git a/dist/icons/achievements/install.svg b/dist/icons/achievements/install.svg new file mode 100644 index 000000000..d26b20615 --- /dev/null +++ b/dist/icons/achievements/install.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + Install + + diff --git a/dist/icons/achievements/install_100.svg b/dist/icons/achievements/install_100.svg new file mode 100644 index 000000000..644038aab --- /dev/null +++ b/dist/icons/achievements/install_100.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + Install100 + + diff --git a/dist/icons/achievements/karma.svg b/dist/icons/achievements/karma.svg new file mode 100644 index 000000000..e0afb441b --- /dev/null +++ b/dist/icons/achievements/karma.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + KARMA + + diff --git a/dist/icons/achievements/maxcores.svg b/dist/icons/achievements/maxcores.svg new file mode 100644 index 000000000..4ad449eb2 --- /dev/null +++ b/dist/icons/achievements/maxcores.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + 8 Cores + + diff --git a/dist/icons/achievements/maxram.svg b/dist/icons/achievements/maxram.svg new file mode 100644 index 000000000..effc927a4 --- /dev/null +++ b/dist/icons/achievements/maxram.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + 2^30GB + + diff --git a/dist/icons/achievements/nf255.svg b/dist/icons/achievements/nf255.svg new file mode 100644 index 000000000..a23672bc9 --- /dev/null +++ b/dist/icons/achievements/nf255.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + NF 255 + + diff --git a/dist/icons/achievements/node.svg b/dist/icons/achievements/node.svg new file mode 100644 index 000000000..576020448 --- /dev/null +++ b/dist/icons/achievements/node.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + HACKNET + + diff --git a/dist/icons/achievements/ns2.svg b/dist/icons/achievements/ns2.svg new file mode 100644 index 000000000..2a75482ed --- /dev/null +++ b/dist/icons/achievements/ns2.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + NS2 + + diff --git a/dist/icons/achievements/p0.svg b/dist/icons/achievements/p0.svg new file mode 100644 index 000000000..9a4f9e6f5 --- /dev/null +++ b/dist/icons/achievements/p0.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + P0 + + diff --git a/dist/icons/achievements/p1.svg b/dist/icons/achievements/p1.svg new file mode 100644 index 000000000..7bb2cf6cb --- /dev/null +++ b/dist/icons/achievements/p1.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + P1 + + diff --git a/dist/icons/achievements/p2.svg b/dist/icons/achievements/p2.svg new file mode 100644 index 000000000..48d861052 --- /dev/null +++ b/dist/icons/achievements/p2.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + P2 + + diff --git a/dist/icons/achievements/p3.svg b/dist/icons/achievements/p3.svg new file mode 100644 index 000000000..cd82ca1ff --- /dev/null +++ b/dist/icons/achievements/p3.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + P3 + + diff --git a/dist/icons/achievements/p4.svg b/dist/icons/achievements/p4.svg new file mode 100644 index 000000000..4a4ed8b48 --- /dev/null +++ b/dist/icons/achievements/p4.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + P4 + + diff --git a/dist/icons/achievements/queue40.svg b/dist/icons/achievements/queue40.svg new file mode 100644 index 000000000..6227323c7 --- /dev/null +++ b/dist/icons/achievements/queue40.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + queue40 + + diff --git a/dist/icons/achievements/reputation.svg b/dist/icons/achievements/reputation.svg new file mode 100644 index 000000000..67e50d7f8 --- /dev/null +++ b/dist/icons/achievements/reputation.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + Reputation10m + + diff --git a/dist/icons/achievements/run1000.svg b/dist/icons/achievements/run1000.svg new file mode 100644 index 000000000..a6d1ba6f3 --- /dev/null +++ b/dist/icons/achievements/run1000.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + RUN1000 + + diff --git a/dist/icons/achievements/thecovenant.svg b/dist/icons/achievements/thecovenant.svg new file mode 100644 index 000000000..5dded05a7 --- /dev/null +++ b/dist/icons/achievements/thecovenant.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + The Covenant + +