mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-25 00:53:52 +01:00
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
This commit is contained in:
parent
4363aa43fe
commit
844d518684
15
assets/Steam/achievements/pack-for-web.sh
Normal file
15
assets/Steam/achievements/pack-for-web.sh
Normal file
@ -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
|
@ -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
|
||||
|
486
src/Achievements/AchievementData.json
Normal file
486
src/Achievements/AchievementData.json
Normal file
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
60
src/Achievements/AchievementEntry.tsx
Normal file
60
src/Achievements/AchievementEntry.tsx
Normal file
@ -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 (
|
||||
<Box sx={{
|
||||
border: `1px solid ${Settings.theme.well}`, mb: 2
|
||||
}}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
}}>
|
||||
<AchievementIcon
|
||||
achievement={achievement} unlocked={isUnlocked} size="72px"
|
||||
colorFilters={isUnlocked ? cssFiltersUnlocked: cssFiltersLocked} />
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
px: 1
|
||||
}}>
|
||||
<Typography variant="h6" sx={{ color: mainColor}}>
|
||||
{achievement.Name}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ maxWidth: '500px', color: mainColor}}>
|
||||
{achievement.Description}
|
||||
</Typography>
|
||||
{isUnlocked && (
|
||||
<Typography variant="caption" sx={{ fontSize: '12px', color: Settings.theme.primarydark }}>
|
||||
Acquired on {achievedOn}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
35
src/Achievements/AchievementIcon.tsx
Normal file
35
src/Achievements/AchievementIcon.tsx
Normal file
@ -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 (
|
||||
<Box
|
||||
sx={{
|
||||
border: `1px solid ${mainColor}`,
|
||||
width: size, height: size,
|
||||
m: 1,
|
||||
visibility: imgLoaded ? 'visible' : 'hidden'
|
||||
}}
|
||||
>
|
||||
<img src={`dist/icons/achievements/${encodeURI(achievement.Icon)}.svg`}
|
||||
style={{ filter: colorFilters, width: size, height: size }}
|
||||
onLoad={() => setImgLoaded(true)}
|
||||
alt={achievement.Name} />
|
||||
</Box>
|
||||
);
|
||||
}
|
121
src/Achievements/AchievementList.tsx
Normal file
121
src/Achievements/AchievementList.tsx
Normal file
@ -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 (
|
||||
<Box sx={{ pr: 18, my: 2 }}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexWrap: 'wrap',
|
||||
}}>
|
||||
{unlocked.length > 0 && (
|
||||
<Accordion defaultExpanded disableGutters square>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h5" sx={{ my: 1 }}>
|
||||
Acquired ({unlocked.length}/{data.length})
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ pt: 2 }}>
|
||||
{unlocked.map(item => (
|
||||
<AchievementEntry key={item.achievement.ID}
|
||||
achievement={item.achievement}
|
||||
unlockedOn={item.unlockedOn}
|
||||
cssFiltersUnlocked={cssPrimary}
|
||||
cssFiltersLocked={cssSecondary} />
|
||||
))}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{locked.length > 0 && (
|
||||
<Accordion disableGutters square>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h5" color="secondary">
|
||||
Locked ({locked.length} remaining)
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ pt: 2 }}>
|
||||
{locked.map(item => (
|
||||
<AchievementEntry key={item.achievement.ID}
|
||||
achievement={item.achievement}
|
||||
cssFiltersUnlocked={cssPrimary}
|
||||
cssFiltersLocked={cssSecondary} />
|
||||
))}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{unavailable.length > 0 && (
|
||||
<Accordion disableGutters square>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h5" color="secondary">
|
||||
Unavailable ({unavailable.length} remaining)
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography sx={{ mt: 1 }}>
|
||||
{unavailable.length} additional achievements hidden behind content you don't have access to.
|
||||
</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{secret.length > 0 && (
|
||||
<Accordion disableGutters square>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h5" color="secondary">
|
||||
Secret ({secret.length} remaining)
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography color="secondary" sx={{ mt: 1 }}>
|
||||
{secret.map(item => (
|
||||
<>
|
||||
<CorruptableText content={item.achievement.ID}></CorruptableText>
|
||||
<br />
|
||||
</>
|
||||
))}
|
||||
</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
768
src/Achievements/Achievements.ts
Normal file
768
src/Achievements/Achievements.ts
Normal file
@ -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 = (<AchievementDataJson><unknown>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<AchievementData>;
|
||||
}
|
||||
|
||||
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<Achievement> = {
|
||||
"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)];
|
||||
}
|
30
src/Achievements/AchievementsRoot.tsx
Normal file
30
src/Achievements/AchievementsRoot.tsx
Normal file
@ -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 (
|
||||
<div className={classes.root} style={{ width: "90%" }}>
|
||||
<Typography variant="h4">Achievements</Typography>
|
||||
<AchievementList achievements={Object.values(achievements)} playerAchievements={Player.achievements} />
|
||||
</div>
|
||||
);
|
||||
}
|
9
src/Achievements/README.md
Normal file
9
src/Achievements/README.md
Normal file
@ -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`
|
415
src/Electron.tsx
415
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();
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ export interface IEngine {
|
||||
messages: number;
|
||||
mechanicProcess: number;
|
||||
contractGeneration: number;
|
||||
achievementsCounter: number;
|
||||
};
|
||||
decrementAllCounters: (numCycles?: number) => void;
|
||||
checkCounters: () => void;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
</Typography>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
key={"Achievements"}
|
||||
className={clsx({
|
||||
[classes.active]: props.page === Page.Achievements,
|
||||
})}
|
||||
onClick={clickAchievements}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<EmojiEventsIcon color={props.page !== Page.Achievements ? "secondary" : "primary"} />
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
<Typography color={props.page !== Page.Achievements ? "secondary" : "primary"}>Achievements</Typography>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
key={"Options"}
|
||||
|
315
src/ThirdParty/colorUtils.ts
vendored
Normal file
315
src/ThirdParty/colorUtils.ts
vendored
Normal file
@ -0,0 +1,315 @@
|
||||
// @ts-nocheck
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
/*
|
||||
Taken from:
|
||||
https://codepen.io/sosuke/pen/Pjoqqp
|
||||
--------------------------------------------------------
|
||||
This utility transform an hex color into css filter rules.
|
||||
Useful to change color of a black image to match a given color
|
||||
*/
|
||||
|
||||
class Color {
|
||||
constructor(r, g, b) {
|
||||
this.set(r, g, b);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)})`;
|
||||
}
|
||||
|
||||
set(r, g, b) {
|
||||
this.r = this.clamp(r);
|
||||
this.g = this.clamp(g);
|
||||
this.b = this.clamp(b);
|
||||
}
|
||||
|
||||
hueRotate(angle = 0) {
|
||||
angle = angle / 180 * Math.PI;
|
||||
const sin = Math.sin(angle);
|
||||
const cos = Math.cos(angle);
|
||||
|
||||
this.multiply([
|
||||
0.213 + cos * 0.787 - sin * 0.213,
|
||||
0.715 - cos * 0.715 - sin * 0.715,
|
||||
0.072 - cos * 0.072 + sin * 0.928,
|
||||
0.213 - cos * 0.213 + sin * 0.143,
|
||||
0.715 + cos * 0.285 + sin * 0.140,
|
||||
0.072 - cos * 0.072 - sin * 0.283,
|
||||
0.213 - cos * 0.213 - sin * 0.787,
|
||||
0.715 - cos * 0.715 + sin * 0.715,
|
||||
0.072 + cos * 0.928 + sin * 0.072,
|
||||
]);
|
||||
}
|
||||
|
||||
grayscale(value = 1) {
|
||||
this.multiply([
|
||||
0.2126 + 0.7874 * (1 - value),
|
||||
0.7152 - 0.7152 * (1 - value),
|
||||
0.0722 - 0.0722 * (1 - value),
|
||||
0.2126 - 0.2126 * (1 - value),
|
||||
0.7152 + 0.2848 * (1 - value),
|
||||
0.0722 - 0.0722 * (1 - value),
|
||||
0.2126 - 0.2126 * (1 - value),
|
||||
0.7152 - 0.7152 * (1 - value),
|
||||
0.0722 + 0.9278 * (1 - value),
|
||||
]);
|
||||
}
|
||||
|
||||
sepia(value = 1) {
|
||||
this.multiply([
|
||||
0.393 + 0.607 * (1 - value),
|
||||
0.769 - 0.769 * (1 - value),
|
||||
0.189 - 0.189 * (1 - value),
|
||||
0.349 - 0.349 * (1 - value),
|
||||
0.686 + 0.314 * (1 - value),
|
||||
0.168 - 0.168 * (1 - value),
|
||||
0.272 - 0.272 * (1 - value),
|
||||
0.534 - 0.534 * (1 - value),
|
||||
0.131 + 0.869 * (1 - value),
|
||||
]);
|
||||
}
|
||||
|
||||
saturate(value = 1) {
|
||||
this.multiply([
|
||||
0.213 + 0.787 * value,
|
||||
0.715 - 0.715 * value,
|
||||
0.072 - 0.072 * value,
|
||||
0.213 - 0.213 * value,
|
||||
0.715 + 0.285 * value,
|
||||
0.072 - 0.072 * value,
|
||||
0.213 - 0.213 * value,
|
||||
0.715 - 0.715 * value,
|
||||
0.072 + 0.928 * value,
|
||||
]);
|
||||
}
|
||||
|
||||
multiply(matrix) {
|
||||
const newR = this.clamp(this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2]);
|
||||
const newG = this.clamp(this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5]);
|
||||
const newB = this.clamp(this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8]);
|
||||
this.r = newR;
|
||||
this.g = newG;
|
||||
this.b = newB;
|
||||
}
|
||||
|
||||
brightness(value = 1) {
|
||||
this.linear(value);
|
||||
}
|
||||
contrast(value = 1) {
|
||||
this.linear(value, -(0.5 * value) + 0.5);
|
||||
}
|
||||
|
||||
linear(slope = 1, intercept = 0) {
|
||||
this.r = this.clamp(this.r * slope + intercept * 255);
|
||||
this.g = this.clamp(this.g * slope + intercept * 255);
|
||||
this.b = this.clamp(this.b * slope + intercept * 255);
|
||||
}
|
||||
|
||||
invert(value = 1) {
|
||||
this.r = this.clamp((value + this.r / 255 * (1 - 2 * value)) * 255);
|
||||
this.g = this.clamp((value + this.g / 255 * (1 - 2 * value)) * 255);
|
||||
this.b = this.clamp((value + this.b / 255 * (1 - 2 * value)) * 255);
|
||||
}
|
||||
|
||||
hsl() {
|
||||
// Code taken from https://stackoverflow.com/a/9493060/2688027, licensed under CC BY-SA.
|
||||
const r = this.r / 255;
|
||||
const g = this.g / 255;
|
||||
const b = this.b / 255;
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
let h, s, l = (max + min) / 2;
|
||||
|
||||
if (max === min) {
|
||||
h = s = 0;
|
||||
} else {
|
||||
const d = max - min;
|
||||
s = l > 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;
|
||||
}
|
@ -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) {
|
||||
|
@ -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 ? (
|
||||
<AchievementsRoot />
|
||||
) : (
|
||||
<>
|
||||
<Typography>Cannot load</Typography>
|
||||
</>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
```
|
||||
|
69
tools/fetch-steam-achievements-data.js
Normal file
69
tools/fetch-steam-achievements-data.js
Normal file
@ -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)));
|
@ -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"]
|
||||
}
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user