diff --git a/assets/Steam/achievements/pack-for-web.sh b/assets/Steam/achievements/pack-for-web.sh index f00431ff2..049ccce0d 100644 --- a/assets/Steam/achievements/pack-for-web.sh +++ b/assets/Steam/achievements/pack-for-web.sh @@ -10,6 +10,6 @@ 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" + 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 000000000..ebb557a1e --- /dev/null +++ b/src/Achievements/AchievementCategory.tsx @@ -0,0 +1,60 @@ +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 }[]; + usePadding?: boolean; +} + +function steamCount(achievements: { achievement: Achievement }[]): number { + return achievements.filter((entry) => !entry.achievement.NotInSteam).length; +} + +export function AchievementCategory({ + title, + achievements, + allAchievements, + usePadding, + children, +}: React.PropsWithChildren): JSX.Element { + /** + * For each achievement, we need to display the icon and the detail on the same "row" (icon on the left and detail on + * the right). When the viewport is to small, the detail part of some achievements is "moved" to a separate "row". It + * looks like this: + * + * + * + * + * + * + * + * + * + * + * + * + * Using "minWidth" fixes this issue by setting a min value for the width of each row + */ + 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 774025657..c6f737fd0 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 de6740a5b..45c9028e1 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 291fa3aaa..104a66f27 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"; @@ -53,80 +54,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) - - - - - {pluralize(unavailable.length, "additional achievement")} hidden behind content you don't have access - to. - - - + + + {pluralize(unavailable.length, "additional achievement")} 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 9b3f28db1..d0e64c1c8 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 0e7eb3b11..2656ab97d 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 ed9fdcc19..76a451681 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 +- 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`