mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-21 23:23:51 +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 index.html .package
|
||||||
cp -r electron/* .package
|
cp -r electron/* .package
|
||||||
cp -r dist/ext .package/dist
|
cp -r dist/ext .package/dist
|
||||||
|
cp -r dist/icons .package/dist
|
||||||
|
|
||||||
# The css files
|
# The css files
|
||||||
cp dist/vendor.css .package/dist
|
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 { Player } from "./Player";
|
||||||
import { Programs } from "./Programs/Programs";
|
|
||||||
import { isScriptFilename } from "./Script/isScriptFilename";
|
import { isScriptFilename } from "./Script/isScriptFilename";
|
||||||
import { Script } from "./Script/Script";
|
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 { removeLeadingSlash } from "./Terminal/DirectoryHelpers";
|
||||||
import { Terminal } from "./Terminal";
|
import { Terminal } from "./Terminal";
|
||||||
import { SnackbarEvents } from "./ui/React/Snackbar";
|
import { SnackbarEvents } from "./ui/React/Snackbar";
|
||||||
import { IMap } from "./types";
|
import { IMap } from "./types";
|
||||||
|
import { GetServer } from "./Server/AllServers";
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initElectron(): void {
|
export function initElectron(): void {
|
||||||
const userAgent = navigator.userAgent.toLowerCase();
|
const userAgent = navigator.userAgent.toLowerCase();
|
||||||
if (userAgent.indexOf(" electron/") > -1) {
|
if (userAgent.indexOf(" electron/") > -1) {
|
||||||
// Electron-specific code
|
// Electron-specific code
|
||||||
setAchievements([]);
|
(document as any).achievements = [];
|
||||||
initWebserver();
|
initWebserver();
|
||||||
setInterval(calculateAchievements, 5000);
|
|
||||||
initAppNotifier();
|
initAppNotifier();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ export interface IEngine {
|
|||||||
messages: number;
|
messages: number;
|
||||||
mechanicProcess: number;
|
mechanicProcess: number;
|
||||||
contractGeneration: number;
|
contractGeneration: number;
|
||||||
|
achievementsCounter: number;
|
||||||
};
|
};
|
||||||
decrementAllCounters: (numCycles?: number) => void;
|
decrementAllCounters: (numCycles?: number) => void;
|
||||||
checkCounters: () => void;
|
checkCounters: () => void;
|
||||||
|
@ -30,6 +30,7 @@ import { IRouter } from "../ui/Router";
|
|||||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||||
import { HacknetServer } from "../Hacknet/HacknetServer";
|
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||||
import { ISkillProgress } from "./formulas/skill";
|
import { ISkillProgress } from "./formulas/skill";
|
||||||
|
import { PlayerAchievement } from "../Achievements/Achievements";
|
||||||
|
|
||||||
export interface IPlayer {
|
export interface IPlayer {
|
||||||
// Class members
|
// Class members
|
||||||
@ -70,6 +71,7 @@ export interface IPlayer {
|
|||||||
sleevesFromCovenant: number;
|
sleevesFromCovenant: number;
|
||||||
sourceFiles: IPlayerOwnedSourceFile[];
|
sourceFiles: IPlayerOwnedSourceFile[];
|
||||||
exploits: Exploit[];
|
exploits: Exploit[];
|
||||||
|
achievements: PlayerAchievement[];
|
||||||
lastUpdate: number;
|
lastUpdate: number;
|
||||||
totalPlaytime: number;
|
totalPlaytime: number;
|
||||||
|
|
||||||
@ -238,6 +240,7 @@ export interface IPlayer {
|
|||||||
takeDamage(amt: number): boolean;
|
takeDamage(amt: number): boolean;
|
||||||
travel(to: CityName): boolean;
|
travel(to: CityName): boolean;
|
||||||
giveExploit(exploit: Exploit): void;
|
giveExploit(exploit: Exploit): void;
|
||||||
|
giveAchievement(achievementId: string): void;
|
||||||
queryStatFromString(str: string): number;
|
queryStatFromString(str: string): number;
|
||||||
getIntelligenceBonus(weight: number): number;
|
getIntelligenceBonus(weight: number): number;
|
||||||
getCasinoWinnings(): number;
|
getCasinoWinnings(): number;
|
||||||
|
@ -35,6 +35,7 @@ import { CityName } from "../../Locations/data/CityNames";
|
|||||||
import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
|
import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
|
||||||
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../../utils/JSONReviver";
|
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../../utils/JSONReviver";
|
||||||
import { ISkillProgress } from "../formulas/skill";
|
import { ISkillProgress } from "../formulas/skill";
|
||||||
|
import { PlayerAchievement } from '../../Achievements/Achievements';
|
||||||
|
|
||||||
export class PlayerObject implements IPlayer {
|
export class PlayerObject implements IPlayer {
|
||||||
// Class members
|
// Class members
|
||||||
@ -75,6 +76,7 @@ export class PlayerObject implements IPlayer {
|
|||||||
sleevesFromCovenant: number;
|
sleevesFromCovenant: number;
|
||||||
sourceFiles: IPlayerOwnedSourceFile[];
|
sourceFiles: IPlayerOwnedSourceFile[];
|
||||||
exploits: Exploit[];
|
exploits: Exploit[];
|
||||||
|
achievements: PlayerAchievement[];
|
||||||
lastUpdate: number;
|
lastUpdate: number;
|
||||||
totalPlaytime: number;
|
totalPlaytime: number;
|
||||||
|
|
||||||
@ -243,6 +245,7 @@ export class PlayerObject implements IPlayer {
|
|||||||
takeDamage: (amt: number) => boolean;
|
takeDamage: (amt: number) => boolean;
|
||||||
travel: (to: CityName) => boolean;
|
travel: (to: CityName) => boolean;
|
||||||
giveExploit: (exploit: Exploit) => void;
|
giveExploit: (exploit: Exploit) => void;
|
||||||
|
giveAchievement: (achievementId: string) => void;
|
||||||
queryStatFromString: (str: string) => number;
|
queryStatFromString: (str: string) => number;
|
||||||
getIntelligenceBonus: (weight: number) => number;
|
getIntelligenceBonus: (weight: number) => number;
|
||||||
getCasinoWinnings: () => number;
|
getCasinoWinnings: () => number;
|
||||||
@ -467,6 +470,7 @@ export class PlayerObject implements IPlayer {
|
|||||||
this.scriptProdSinceLastAug = 0;
|
this.scriptProdSinceLastAug = 0;
|
||||||
|
|
||||||
this.exploits = [];
|
this.exploits = [];
|
||||||
|
this.achievements = [];
|
||||||
|
|
||||||
this.init = generalMethods.init;
|
this.init = generalMethods.init;
|
||||||
this.prestigeAugmentation = generalMethods.prestigeAugmentation;
|
this.prestigeAugmentation = generalMethods.prestigeAugmentation;
|
||||||
@ -557,6 +561,7 @@ export class PlayerObject implements IPlayer {
|
|||||||
this.gotoLocation = generalMethods.gotoLocation;
|
this.gotoLocation = generalMethods.gotoLocation;
|
||||||
this.canAccessResleeving = generalMethods.canAccessResleeving;
|
this.canAccessResleeving = generalMethods.canAccessResleeving;
|
||||||
this.giveExploit = generalMethods.giveExploit;
|
this.giveExploit = generalMethods.giveExploit;
|
||||||
|
this.giveAchievement = generalMethods.giveAchievement;
|
||||||
this.getIntelligenceBonus = generalMethods.getIntelligenceBonus;
|
this.getIntelligenceBonus = generalMethods.getIntelligenceBonus;
|
||||||
this.getCasinoWinnings = generalMethods.getCasinoWinnings;
|
this.getCasinoWinnings = generalMethods.getCasinoWinnings;
|
||||||
this.hasAugmentation = augmentationMethods.hasAugmentation;
|
this.hasAugmentation = augmentationMethods.hasAugmentation;
|
||||||
|
@ -64,6 +64,7 @@ import React from "react";
|
|||||||
import { serverMetadata } from "../../Server/data/servers";
|
import { serverMetadata } from "../../Server/data/servers";
|
||||||
import { SnackbarEvents } from "../../ui/React/Snackbar";
|
import { SnackbarEvents } from "../../ui/React/Snackbar";
|
||||||
import { calculateClassEarnings } from "../formulas/work";
|
import { calculateClassEarnings } from "../formulas/work";
|
||||||
|
import { achievements } from "../../Achievements/Achievements";
|
||||||
|
|
||||||
export function init(this: IPlayer): void {
|
export function init(this: IPlayer): void {
|
||||||
/* Initialize Player's home computer */
|
/* 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 {
|
export function getIntelligenceBonus(this: IPlayer, weight: number): number {
|
||||||
return calculateIntelligenceBonus(this.intelligence, weight);
|
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 HelpIcon from "@mui/icons-material/Help"; // Tutorial
|
||||||
import SettingsIcon from "@mui/icons-material/Settings"; // options
|
import SettingsIcon from "@mui/icons-material/Settings"; // options
|
||||||
import DeveloperBoardIcon from "@mui/icons-material/DeveloperBoard"; // Dev
|
import DeveloperBoardIcon from "@mui/icons-material/DeveloperBoard"; // Dev
|
||||||
|
import EmojiEventsIcon from "@mui/icons-material/EmojiEvents"; // Achievements
|
||||||
import AccountBoxIcon from "@mui/icons-material/AccountBox";
|
import AccountBoxIcon from "@mui/icons-material/AccountBox";
|
||||||
import PublicIcon from "@mui/icons-material/Public";
|
import PublicIcon from "@mui/icons-material/Public";
|
||||||
import LiveHelpIcon from "@mui/icons-material/LiveHelp";
|
import LiveHelpIcon from "@mui/icons-material/LiveHelp";
|
||||||
@ -256,6 +257,10 @@ export function SidebarRoot(props: IProps): React.ReactElement {
|
|||||||
props.router.toDevMenu();
|
props.router.toDevMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clickAchievements(): void {
|
||||||
|
props.router.toAchievements();
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Shortcuts to navigate through the game
|
// Shortcuts to navigate through the game
|
||||||
// Alt-t - Terminal
|
// Alt-t - Terminal
|
||||||
@ -747,6 +752,21 @@ export function SidebarRoot(props: IProps): React.ReactElement {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</ListItem>
|
</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
|
<ListItem
|
||||||
button
|
button
|
||||||
key={"Options"}
|
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 { exceptionAlert } from "./utils/helpers/exceptionAlert";
|
||||||
|
|
||||||
import { startExploits } from "./Exploits/loops";
|
import { startExploits } from "./Exploits/loops";
|
||||||
|
import { calculateAchievements } from "./Achievements/Achievements";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { setupUncaughtPromiseHandler } from "./UncaughtPromiseHandler";
|
import { setupUncaughtPromiseHandler } from "./UncaughtPromiseHandler";
|
||||||
@ -65,6 +66,7 @@ const Engine: {
|
|||||||
messages: number;
|
messages: number;
|
||||||
mechanicProcess: number;
|
mechanicProcess: number;
|
||||||
contractGeneration: number;
|
contractGeneration: number;
|
||||||
|
achievementsCounter: number;
|
||||||
};
|
};
|
||||||
decrementAllCounters: (numCycles?: number) => void;
|
decrementAllCounters: (numCycles?: number) => void;
|
||||||
checkCounters: () => void;
|
checkCounters: () => void;
|
||||||
@ -163,6 +165,7 @@ const Engine: {
|
|||||||
messages: 150,
|
messages: 150,
|
||||||
mechanicProcess: 5, // Processes certain mechanics (Corporation, Bladeburner)
|
mechanicProcess: 5, // Processes certain mechanics (Corporation, Bladeburner)
|
||||||
contractGeneration: 3000, // Generate Coding Contracts
|
contractGeneration: 3000, // Generate Coding Contracts
|
||||||
|
achievementsCounter: 300, // Check if we have new achievements
|
||||||
},
|
},
|
||||||
|
|
||||||
decrementAllCounters: function (numCycles = 1) {
|
decrementAllCounters: function (numCycles = 1) {
|
||||||
@ -234,6 +237,11 @@ const Engine: {
|
|||||||
}
|
}
|
||||||
Engine.Counters.contractGeneration = 3000;
|
Engine.Counters.contractGeneration = 3000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Engine.Counters.achievementsCounter <= 0) {
|
||||||
|
calculateAchievements();
|
||||||
|
Engine.Counters.achievementsCounter = 300;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
load: function (saveString) {
|
load: function (saveString) {
|
||||||
|
@ -76,6 +76,7 @@ import { InvitationModal } from "../Faction/ui/InvitationModal";
|
|||||||
import { enterBitNode } from "../RedPill";
|
import { enterBitNode } from "../RedPill";
|
||||||
import { Context } from "./Context";
|
import { Context } from "./Context";
|
||||||
import { RecoveryMode, RecoveryRoot } from "./React/RecoveryRoot";
|
import { RecoveryMode, RecoveryRoot } from "./React/RecoveryRoot";
|
||||||
|
import { AchievementsRoot } from "../Achievements/AchievementsRoot";
|
||||||
|
|
||||||
const htmlLocation = location;
|
const htmlLocation = location;
|
||||||
|
|
||||||
@ -183,6 +184,9 @@ export let Router: IRouter = {
|
|||||||
toStaneksGift: () => {
|
toStaneksGift: () => {
|
||||||
throw new Error("Router called before initialization");
|
throw new Error("Router called before initialization");
|
||||||
},
|
},
|
||||||
|
toAchievements: () => {
|
||||||
|
throw new Error("Router called before initialization");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function determineStartPage(player: IPlayer): Page {
|
function determineStartPage(player: IPlayer): Page {
|
||||||
@ -287,6 +291,9 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
|||||||
toStaneksGift: () => {
|
toStaneksGift: () => {
|
||||||
setPage(Page.StaneksGift);
|
setPage(Page.StaneksGift);
|
||||||
},
|
},
|
||||||
|
toAchievements: () => {
|
||||||
|
setPage(Page.Achievements);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -408,7 +415,9 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
|||||||
Router.toTerminal();
|
Router.toTerminal();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : page === Page.Achievements ? (
|
||||||
|
<AchievementsRoot />
|
||||||
|
) : (
|
||||||
<>
|
<>
|
||||||
<Typography>Cannot load</Typography>
|
<Typography>Cannot load</Typography>
|
||||||
</>
|
</>
|
||||||
|
@ -36,6 +36,7 @@ export enum Page {
|
|||||||
Loading,
|
Loading,
|
||||||
StaneksGift,
|
StaneksGift,
|
||||||
Recovery,
|
Recovery,
|
||||||
|
Achievements,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScriptEditorRouteOptions {
|
export interface ScriptEditorRouteOptions {
|
||||||
@ -80,4 +81,5 @@ export interface IRouter {
|
|||||||
toBladeburnerCinematic(): void;
|
toBladeburnerCinematic(): void;
|
||||||
toLocation(location: Location): void;
|
toLocation(location: Location): void;
|
||||||
toStaneksGift(): 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
|
```sh
|
||||||
node ./pretty-save.js 'C:\\Users\\martin\\Desktop\\bitburnerSave_1641395736_BN12x14.json' 'C:\\Users\\martin\\Desktop\\pretty.json'
|
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",
|
"target": "es6",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"types": ["cypress", "@testing-library/cypress", "node", "raw-loader.d.ts"]
|
"resolveJsonModule": true,
|
||||||
|
"types": ["cypress", "@testing-library/cypress", "node"]
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
@ -142,7 +142,7 @@ module.exports = (env, argv) => {
|
|||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.(js|jsx|ts|tsx)$/,
|
test: /\.(js$|jsx|ts|tsx)$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
use: {
|
use: {
|
||||||
loader: "babel-loader",
|
loader: "babel-loader",
|
||||||
|
Loading…
Reference in New Issue
Block a user