diff --git a/assets/Steam/achievements/pack-for-web-mac.sh b/assets/Steam/achievements/pack-for-web-mac.sh new file mode 100644 index 0000000000..dcff074b6c --- /dev/null +++ b/assets/Steam/achievements/pack-for-web-mac.sh @@ -0,0 +1,16 @@ +#!/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 + # MacOS uses FreeBSD-style sed instead of GNU sed + sed -i '' "s/fill:#000000;/fill-opacity: 0%;/g" "$i" + sed -i '' "s/fill:#00ff00;/fill:#000000;/g" "$i" +done diff --git a/src/Achievements/AchievementCategory.tsx b/src/Achievements/AchievementCategory.tsx new file mode 100644 index 0000000000..616310a9b8 --- /dev/null +++ b/src/Achievements/AchievementCategory.tsx @@ -0,0 +1,46 @@ +import React from "react"; + +import { Accordion, AccordionSummary, AccordionDetails, Typography } from "@mui/material"; + +import { Achievement } from "./Achievements"; + +interface IProps { + title: string; + achievements: { achievement: Achievement }[]; + allAchievements?: { achievement: Achievement }[]; + sx?: boolean; +} + +function steamCount(achievements: { achievement: Achievement }[]): number { + return achievements.filter((entry) => !entry.achievement.NotInSteam).length; +} + +export function AchievementCategory({ + title, + achievements, + allAchievements, + sx, + children, +}: React.PropsWithChildren): JSX.Element { + // Most parts of the four categories in the old code were very similar (besides the content of + // AccordianDetails), with the Acquired category having a few differences, + // although both the Acquired and Locked categories also had an extra prop in the AccordianDetails. + // The 264px minWidth feels scuffed, but fixes an edge case. + return ( + + + {allAchievements ? ( + + {title} ({achievements.length}/{allAchievements.length}, {steamCount(achievements)}/ + {steamCount(allAchievements)} for Steam) + + ) : ( + + {title} ({achievements.length} remaining, {steamCount(achievements)} for Steam) + + )} + + {children} + + ); +} diff --git a/src/Achievements/AchievementData.json b/src/Achievements/AchievementData.json index 7740256572..c6f737fd04 100644 --- a/src/Achievements/AchievementData.json +++ b/src/Achievements/AchievementData.json @@ -1,6 +1,5 @@ { - "note": "***** Generated from a script, overwritten by steam achievements data *****", - "fetchedOn": 1641517584274, + "note": "Originally generated by a script using Steam achievement data. Going forward, must be edited manually.", "achievements": { "CYBERSEC": { "ID": "CYBERSEC", diff --git a/src/Achievements/AchievementEntry.tsx b/src/Achievements/AchievementEntry.tsx index de6740a5bd..45c9028e1a 100644 --- a/src/Achievements/AchievementEntry.tsx +++ b/src/Achievements/AchievementEntry.tsx @@ -1,6 +1,7 @@ import React from "react"; import { Box, Typography } from "@mui/material"; +import LinkOffIcon from "@mui/icons-material/LinkOff"; import { Achievement } from "./Achievements"; import { Settings } from "../Settings/Settings"; @@ -23,6 +24,7 @@ export function AchievementEntry({ const isUnlocked = !!unlockedOn; const mainColor = isUnlocked ? Settings.theme.primary : Settings.theme.secondarylight; + const captionColor = isUnlocked ? Settings.theme.primarydark : Settings.theme.secondary; let achievedOn = ""; if (unlockedOn) { @@ -64,10 +66,25 @@ export function AchievementEntry({ {achievement.Description} {isUnlocked && ( - + Acquired on {achievedOn} )} + {achievement.NotInSteam && ( + + + + No equivalent Steam achievement + + + )} diff --git a/src/Achievements/AchievementList.tsx b/src/Achievements/AchievementList.tsx index 74d574bd7c..53bc1ce6f7 100644 --- a/src/Achievements/AchievementList.tsx +++ b/src/Achievements/AchievementList.tsx @@ -1,7 +1,8 @@ import React from "react"; -import { Accordion, AccordionSummary, AccordionDetails, Box, Typography } from "@mui/material"; +import { Box, Typography } from "@mui/material"; +import { AchievementCategory } from "./AchievementCategory"; import { AchievementEntry } from "./AchievementEntry"; import { Achievement, PlayerAchievement } from "./Achievements"; import { Settings } from "../Settings/Settings"; @@ -52,79 +53,48 @@ export function AchievementList({ achievements, playerAchievements }: IProps): J }} > {unlocked.length > 0 && ( - - - - Acquired ({unlocked.length}/{data.length}) - - - - {unlocked.map((item) => ( - - ))} - - + + {unlocked.map((item) => ( + + ))} + )} - {locked.length > 0 && ( - - - - Locked ({locked.length} remaining) - - - - {locked.map((item) => ( - - ))} - - + + {locked.map((item) => ( + + ))} + )} - {unavailable.length > 0 && ( - - - - Unavailable ({unavailable.length} remaining) - - - - - {unavailable.length} additional achievements hidden behind content you don't have access to. - - - + + + {unavailable.length} additional achievements hidden behind content you don't have access to. + + )} - {secret.length > 0 && ( - - - - Secret ({secret.length} remaining) - - - - - {secret.map((item) => ( - - -
-
- ))} -
-
-
+ + + {secret.map((item) => ( + + +
+
+ ))} +
+
)} diff --git a/src/Achievements/Achievements.ts b/src/Achievements/Achievements.ts index bbf32092a3..7a52b92d18 100644 --- a/src/Achievements/Achievements.ts +++ b/src/Achievements/Achievements.ts @@ -41,6 +41,7 @@ export interface Achievement { Name?: string; Description?: string; Secret?: boolean; + NotInSteam?: boolean; Condition: () => boolean; Visible?: () => boolean; AdditionalUnlock?: string[]; // IDs of achievements that should be awarded when awarding this one diff --git a/src/Achievements/AchievementsRoot.tsx b/src/Achievements/AchievementsRoot.tsx index 0e7eb3b11a..2656ab97df 100644 --- a/src/Achievements/AchievementsRoot.tsx +++ b/src/Achievements/AchievementsRoot.tsx @@ -1,26 +1,30 @@ import React from "react"; -import { Theme } from "@mui/material/styles"; import { AchievementList } from "./AchievementList"; import { achievements } from "./Achievements"; -import { Typography } from "@mui/material"; +import { Box, Typography } from "@mui/material"; import { Player } from "@player"; import { makeStyles } from "tss-react/mui"; -const useStyles = makeStyles()((theme: Theme) => ({ +const useStyles = makeStyles()({ root: { width: 50, - padding: theme.spacing(2), userSelect: "none", }, -})); +}); export function AchievementsRoot(): JSX.Element { const { classes } = useStyles(); return (
Achievements - + + + Achievements are persistent rewards for various actions and challenges. A limited number of Bitburner + achievements have corresponding achievements in Steam. + + +
); } diff --git a/src/Achievements/README.md b/src/Achievements/README.md index ed9fdcc191..c6df7d9747 100644 --- a/src/Achievements/README.md +++ b/src/Achievements/README.md @@ -1,9 +1,11 @@ # 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 +- If making a Steam achievement, create the achievement in Steam Dev Portal +- Run `sh ./assets/Steam/achievements/pack-for-web.sh`, or `pack-for-web-mac.sh` for MacOS +- Add an entry in `./src/Achievements/AchievementData.json` -> achievements + - It should match the information for the Steam achievement, if applicable + - Order the new achievement entry thematically - Add an entry in `./src/Achievements/Achievements.ts` -> achievements -- Commit `./dist/icons/achievements` & `./src/Achievements/AchievementData.json` + - Match the order of achievements in `AchievementData.json` +- Commit `./dist/icons/achievements`