From 163715db215545300d9c96ea446f9104700773d2 Mon Sep 17 00:00:00 2001 From: Sam Gaus Date: Tue, 18 Jul 2023 17:46:36 +0100 Subject: [PATCH] Show a clan dungeon tab with some basic info. More to come! --- packages/greenbox-data/data/items.ts | 60 +++++++++ packages/greenbox-data/lib/index.ts | 4 + packages/greenbox-data/lib/items.ts | 20 +++ packages/greenbox-script/build.mjs | 15 ++- packages/greenbox-script/src/greenbox.ts | 24 +++- .../src/components/DreadsylvaniaSkills.tsx | 116 +++++++---------- .../src/components/HobopolisSection.tsx | 120 +++++++++++++++++- .../greenbox-web/src/components/IotMs.tsx | 6 +- .../greenbox-web/src/components/ItemGrid.tsx | 33 ++--- packages/greenbox-web/src/components/Path.tsx | 32 +++-- .../greenbox-web/src/components/Paths.tsx | 24 ++-- .../greenbox-web/src/components/Section.tsx | 4 +- .../greenbox-web/src/components/Skill.tsx | 17 ++- .../src/components/SkillBucket.tsx | 22 ++++ .../src/components/SkillClassHeading.tsx | 24 ---- .../greenbox-web/src/components/Skills.tsx | 102 +++++++-------- .../src/components/SlimeTubeSection.tsx | 18 ++- .../src/components/Subsection.tsx | 24 ++++ packages/greenbox-web/src/hooks.ts | 21 --- packages/greenbox-web/src/store/index.ts | 35 ++++- packages/greenbox-web/src/utils.ts | 8 +- 21 files changed, 489 insertions(+), 240 deletions(-) create mode 100644 packages/greenbox-data/data/items.ts create mode 100644 packages/greenbox-web/src/components/SkillBucket.tsx delete mode 100644 packages/greenbox-web/src/components/SkillClassHeading.tsx create mode 100644 packages/greenbox-web/src/components/Subsection.tsx diff --git a/packages/greenbox-data/data/items.ts b/packages/greenbox-data/data/items.ts new file mode 100644 index 0000000..81bd832 --- /dev/null +++ b/packages/greenbox-data/data/items.ts @@ -0,0 +1,60 @@ +export const specialItems = [ + 3138, // Hodgman's whackin' stick + 3139, // Hodgman's imaginary hamster + 3140, // old soft shoes + 3246, // Ol' Scratch's ol' britches + 3247, // Ol' Scratch's stovepipe hat + 3248, // Ol' Scratch's ash can + 3251, // Frosty's old silk hat + 3252, // Frosty's carrot + 3253, // Frosty's nailbat + 3254, // Oscus's pelt + 3255, // Wand of Oscus + 3256, // Oscus's dumpster waders + 3257, // Zombo's skullcap + 3258, // Zombo's shield + 3259, // Zombo's grievous greaves + 3260, // Chester's moustache + 3261, // Chester's bag of candy + 3262, // Chester's cutoffs + 3286, // Frosty's snowball sack + 3310, // sealskin drum + 3311, // washboard shield + 3312, // spaghetti-box banjo + 3313, // marinara jug + 3314, // makeshift castanets + 3315, // left-handed melodica + 3328, // crumpled felt fedora + 3329, // battered old top-hat + 3330, // shapeless wide-brimmed hat + 3331, // mostly rat-hide + 3332, // hobo dungarees + 3333, // old patched suit-pants + 3334, // hobo stogie + 3335, // rope with some soap on it + 3380, // Ol' Scratch's infernal pitchfork + 3381, // Ol' Scratch's stove door + 3382, // Ol' Scratch's manacles + 3383, // Chester's sunglasses + 3384, // Chester's muscle shirt + 3385, // Chester's Aquarius medallion + 3386, // Zombo's shoulder blade + 3387, // Zombo's skull ring + 3388, // Zombo's empty eye + 3389, // Frosty's arm + 3390, // Staff of the Deepest Freeze + 3391, // Frosty's iceball + 3392, // Oscus's garbage can lid + 3393, // Oscus's neverending soda + 3394, // Oscus's flypaper pants + 3395, // Hodgman's porkpie hat + 3396, // Hodgman's lobsterskin pants + 3397, // Hodgman's bow tie + 3405, // Hodgman's lucky sock + 3406, // Hodgman's varcolac paw + 3407, // Hodgman's almanac + 3408, // Hodgman's harmonica + 3409, // Hodgman's garbage sticker + 3410, // Hodgman's metal detector + 3411, // Hodgman's cane +] as const; diff --git a/packages/greenbox-data/lib/index.ts b/packages/greenbox-data/lib/index.ts index a7b2cfd..2ee2b4a 100644 --- a/packages/greenbox-data/lib/index.ts +++ b/packages/greenbox-data/lib/index.ts @@ -2,6 +2,7 @@ import jsoncrush from "jsoncrush"; import { compressFamiliars, expandFamiliars, RawFamiliar } from "./familiars"; import { compressIotMs, expandIotMs, RawIotM } from "./iotms"; +import { compressItems, expandItems, RawItem } from "./items"; import { compressMeta, expandMeta, Meta } from "./meta"; import { compressPaths, expandPaths, RawPath } from "./paths"; import { compressSkills, expandSkills, RawSkill } from "./skills"; @@ -26,6 +27,7 @@ export interface RawSnapshotData { outfitTattoos: RawOutfitTattoo[]; paths: RawPath[]; iotms: RawIotM[]; + items: RawItem[]; } export type CompressedSnapshotData = { [key in keyof RawSnapshotData]: string }; @@ -39,6 +41,7 @@ export function compress(raw: RawSnapshotData): string { outfitTattoos: compressOutfitTattoos(raw.outfitTattoos), paths: compressPaths(raw.paths), iotms: compressIotMs(raw.iotms), + items: compressItems(raw.items), }; const compressedString = JSON.stringify(compressed); @@ -57,5 +60,6 @@ export function expand(encoded: string): RawSnapshotData { outfitTattoos: expandOutfitTattoos(compressed.outfitTattoos), paths: expandPaths(compressed.paths), iotms: expandIotMs(compressed.iotms), + items: expandItems(compressed.items), }; } diff --git a/packages/greenbox-data/lib/items.ts b/packages/greenbox-data/lib/items.ts index 6f377c5..a7c8f7b 100644 --- a/packages/greenbox-data/lib/items.ts +++ b/packages/greenbox-data/lib/items.ts @@ -1,3 +1,5 @@ +import { specialItems } from "../data/items"; + import { loadMafiaData } from "./utils"; export type ItemDef = { @@ -27,3 +29,21 @@ export const loadItems = async (lastKnownSize: number) => { data: raw.data.filter((p) => p.length > 2).map(parseItem), }; }; + +export type RawItem = [id: number, quantity: number]; + +export const compressItems = (items: RawItem[]) => { + return items + .filter(([, q]) => q > 0) + .map(([id, q]) => `${id}${q > 1 ? `:${q}` : ""}`) + .sort() + .join(","); +}; + +export const expandItems = (s = "") => + s.split(",").map((l) => { + const parts = l.split(":"); + return [Number(parts[0]), parts[1] ? Number(parts[1]) : 1] as RawItem; + }); + +export { specialItems }; diff --git a/packages/greenbox-script/build.mjs b/packages/greenbox-script/build.mjs index 5668604..71c10fa 100644 --- a/packages/greenbox-script/build.mjs +++ b/packages/greenbox-script/build.mjs @@ -7,6 +7,19 @@ const args = process.argv.slice(2); const watch = args.some((a) => a === "--watch" || a === "-w"); +const watchPlugin = { + name: "watch", + setup(build) { + if (!watch) return; + build.onEnd((result) => { + const date = new Date(); + console.log( + `[${date.toISOString()}] Build ${result.errors.length ? "failed" : "succeeded"}.`, + ); + }); + }, +}; + const context = await esbuild.context({ entryPoints: { greenbox: "src/greenbox.ts", @@ -18,7 +31,7 @@ const context = await esbuild.context({ platform: "node", target: "rhino1.7.14", external: ["kolmafia"], - plugins: [babel()], + plugins: [babel(), watchPlugin], outdir: "dist/scripts/greenbox", loader: { ".json": "text" }, inject: ["./kolmafia-polyfill.js"], diff --git a/packages/greenbox-script/src/greenbox.ts b/packages/greenbox-script/src/greenbox.ts index dca1cbc..5c4dcd8 100644 --- a/packages/greenbox-script/src/greenbox.ts +++ b/packages/greenbox-script/src/greenbox.ts @@ -11,11 +11,13 @@ import { PathDef, RawFamiliar, RawIotM, + RawItem, RawOutfitTattoo, RawPath, RawSkill, RawTrophy, SkillStatus, + specialItems, TattooDef, TattooStatus, TrophyDef, @@ -55,6 +57,21 @@ function checkIotMs(options: IotMOptions) { ); } +/** + * Generates a list of items and ownership status. + * @returns array of 2-tuples of item id and status + */ + +function checkItems(options: { force: number[] }) { + return specialItems.map( + (id) => + [ + id, + options.force.includes(id) || haveItem(Item.get(id)) ? ItemStatus.HAVE : ItemStatus.NONE, + ] as RawItem, + ); +} + /** * Generates a list of all permable skills and their status. * @returns list of 3-tuples of skill id, perm status and skill level, if any @@ -255,8 +272,8 @@ function main(args = ""): void { const tattoos = visitUrl("account_tattoos.php"); - const forceIotMs = []; - if (hasFlag(args, "--force-florist")) forceIotMs.push(6413); + const forceItems = []; + if (hasFlag(args, "--force-florist")) forceItems.push(6413); const code = compress({ meta: checkMeta(), @@ -265,7 +282,8 @@ function main(args = ""): void { trophies: checkTrophies(), ...checkTattoos(tattoos), paths: checkPaths(tattoos), - iotms: checkIotMs({ force: forceIotMs }), + iotms: checkIotMs({ force: forceItems }), + items: checkItems({ force: forceItems }), }); const link = `https://greenbox.loathers.net/?${keepPrivate ? `d=${code}` : `u=${myId()}`}`; diff --git a/packages/greenbox-web/src/components/DreadsylvaniaSkills.tsx b/packages/greenbox-web/src/components/DreadsylvaniaSkills.tsx index 34c7a54..9729a4f 100644 --- a/packages/greenbox-web/src/components/DreadsylvaniaSkills.tsx +++ b/packages/greenbox-web/src/components/DreadsylvaniaSkills.tsx @@ -1,64 +1,16 @@ -import { - Heading, - HStack, - Stack, - TableContainer, - Table, - Tbody, - Th, - Thead, - Td, - Tr, -} from "@chakra-ui/react"; -import { SkillStatus } from "greenbox-data"; -import { useMemo } from "react"; - -import { useAppSelector } from "../hooks"; -import { createPlayerDataSelector } from "../store"; +import { TableContainer, Table, Tbody, Th, Thead, Td, Tr } from "@chakra-ui/react"; import AlphaImage from "./AlphaImage"; import Skill from "./Skill"; +import Subsection from "./Subsection"; function BlankCell() { return ; } -const selectPlayerSkills = createPlayerDataSelector("skills"); - export default function DreadsylvaniaSkills() { - const playerSkills = useAppSelector(selectPlayerSkills); - const skills = useAppSelector((state) => state.skills); - const dreadSkills = useMemo( - () => - skills - .filter((s) => s.id >= 92 && s.id <= 106) - .reduce( - (acc, s) => ({ ...acc, [s.id]: s }), - {} as { [id: number]: (typeof skills)[number] }, - ), - [skills], - ); - const idToSkill = useMemo( - () => - playerSkills.reduce( - (acc, s) => ({ ...acc, [s[0]]: s }), - {} as { [id: number]: (typeof playerSkills)[number] }, - ), - [playerSkills], - ); - - const skillFor = (id: number) => ( - - ); - return ( - - - - - The Machine - - + @@ -76,7 +28,9 @@ export default function DreadsylvaniaSkills() { - + @@ -84,39 +38,67 @@ export default function DreadsylvaniaSkills() { - - + + - - - + + + - - - - + + + + - - - - - + + + + +
TT{skillFor(92)} + +
PA{skillFor(93)}{skillFor(97)} + + + +
S{skillFor(94)}{skillFor(98)}{skillFor(101)} + + + + + +
DB{skillFor(95)}{skillFor(99)}{skillFor(102)}{skillFor(104)} + + + + + + + +
AT{skillFor(96)}{skillFor(100)}{skillFor(103)}{skillFor(105)}{skillFor(106)} + + + + + + + + + +
-
+ ); } diff --git a/packages/greenbox-web/src/components/HobopolisSection.tsx b/packages/greenbox-web/src/components/HobopolisSection.tsx index 0270c5e..e9ecd7d 100644 --- a/packages/greenbox-web/src/components/HobopolisSection.tsx +++ b/packages/greenbox-web/src/components/HobopolisSection.tsx @@ -1,9 +1,127 @@ +import { SimpleGrid } from "@chakra-ui/react"; +import { SkillStatus } from "greenbox-data"; +import { useMemo } from "react"; + +import { useAppSelector } from "../hooks"; +import { selectIdToPlayerItems } from "../store"; + +import ItemGrid from "./ItemGrid"; import Section from "./Section"; +import Skill from "./Skill"; +import Subsection from "./Subsection"; + +const JOURNAL_SKILLS = [ + 38, // Natural Born Scrabbler + 39, // Thrift and Grift + 40, // Abs of Tin + 41, // Marginally Insane +]; + +const WEAK_ELEMENTAL = [ + 29, // Conjure Relaxing Campfire + 31, // Maximum Chill + 33, // Mudbath + 37, // Inappropriate Backrub + 43, // Creepy Lullaby +] as const; + +const STRONG_ELEMENTAL = [ + 28, // Awesome Balls of Fire + 30, // Snowclone + 32, // Eggsplosion + 36, // Grease Lightning + 42, // Raise Backup Dancer +] as const; + +const CHESTER = [ + 3260, // Chester's moustache + 3261, // Chester's bag of candy + 3262, // Chester's cutoffs + 3383, // Chester's sunglasses + 3384, // Chester's muscle shirt + 3385, // Chester's Aquarius medallion +]; + +const FROSTY = [ + 3251, // Frosty's old silk hat + 3252, // Frosty's carrot + 3253, // Frosty's nailbat + 3286, // Frosty's snowball sack + 3389, // Frosty's arm + 3391, // Frosty's iceball +]; + +const OL_SCRATCH = [ + 3246, // Ol' Scratch's ol' britches + 3247, // Ol' Scratch's stovepipe hat + 3248, // Ol' Scratch's ash can + 3380, // Ol' Scratch's infernal pitchfork + 3381, // Ol' Scratch's stove door + 3382, // Ol' Scratch's manacles +]; + +const OSCUS = [ + 3254, // Oscus's pelt + 3255, // Wand of Oscus + 3256, // Oscus's dumpster waders + 3392, // Oscus's garbage can lid + 3393, // Oscus's neverending soda + 3394, // Oscus's flypaper pants +]; + +const ZOMBO = [ + 3257, // Zombo's skullcap + 3258, // Zombo's shield + 3259, // Zombo's grievous greaves + 3386, // Zombo's shoulder blade + 3387, // Zombo's skull ring + 3388, // Zombo's empty eye +]; export default function HobopolisSection() { + const playerItems = useAppSelector(selectIdToPlayerItems); + return (
- Coming soon! + + + {JOURNAL_SKILLS.map((id) => ( + + ))} + + + + + {WEAK_ELEMENTAL.map((id) => ( + + ))} + + + + + {STRONG_ELEMENTAL.map((id) => ( + + ))} + + + + playerItems[id]?.[1] ?? 0)} /> + + + playerItems[id]?.[1] ?? 0)} /> + + + playerItems[id]?.[1] ?? 0)} + /> + + + playerItems[id]?.[1] ?? 0)} /> + + + playerItems[id]?.[1] ?? 0)} /> +
); } diff --git a/packages/greenbox-web/src/components/IotMs.tsx b/packages/greenbox-web/src/components/IotMs.tsx index 160ac8c..50f6490 100644 --- a/packages/greenbox-web/src/components/IotMs.tsx +++ b/packages/greenbox-web/src/components/IotMs.tsx @@ -2,7 +2,7 @@ import { Box, SimpleGrid } from "@chakra-ui/react"; import { IotMStatus } from "greenbox-data"; import { useMemo } from "react"; -import { useAppSelector, useItemMap } from "../hooks"; +import { useAppSelector } from "../hooks"; import { createPlayerDataSelector } from "../store"; import { chunk, notNullOrUndefined } from "../utils"; @@ -16,9 +16,7 @@ export default function IotMs() { const playerIotMs = useAppSelector(selectPlayerIotMs); const iotms = useAppSelector((state) => state.iotms); const loading = useAppSelector((state) => state.loading.iotms || false); - - // Put together a map of item ids to item definitions for this Path - const idToItem = useItemMap(iotms.map((i) => i.id)); + const idToItem = useAppSelector((state) => state.items); const vipIotMs = useMemo(() => iotms.filter((i) => i.type === "vip").map((i) => i.id), [iotms]); const ownsVipKey = useMemo( diff --git a/packages/greenbox-web/src/components/ItemGrid.tsx b/packages/greenbox-web/src/components/ItemGrid.tsx index 138b470..eae5d35 100644 --- a/packages/greenbox-web/src/components/ItemGrid.tsx +++ b/packages/greenbox-web/src/components/ItemGrid.tsx @@ -1,6 +1,7 @@ import { SimpleGrid } from "@chakra-ui/react"; -import { ItemDef } from "greenbox-data"; +import { ItemDef, ItemStatus, RawItem } from "greenbox-data"; +import { useAppSelector } from "../hooks"; import { itemStatusToThingState } from "../utils"; import Thing from "./Thing"; @@ -8,25 +9,27 @@ import Thing from "./Thing"; type Props = { items: number[]; playerItems: number[]; - idToItem: { [id: number]: ItemDef }; columns?: number; }; -export default function ItemGrid({ items, playerItems, idToItem, columns = 6 }: Props) { +export default function ItemGrid({ items, playerItems, columns = 6 }: Props) { + const idToItem = useAppSelector((state) => state.items); + return ( - {items.map( - (i, index) => - idToItem[i] && ( - - ), - )} + {items.map((id, index) => { + const item = idToItem[id]; + if (!item) return null; + return ( + + ); + })} ); } diff --git a/packages/greenbox-web/src/components/Path.tsx b/packages/greenbox-web/src/components/Path.tsx index 2574ba2..45c434e 100644 --- a/packages/greenbox-web/src/components/Path.tsx +++ b/packages/greenbox-web/src/components/Path.tsx @@ -1,11 +1,11 @@ -import { Badge, Box, Heading, HStack, Stack } from "@chakra-ui/react"; +import { Badge, Box, Heading } from "@chakra-ui/react"; import { ItemStatus, PathDef } from "greenbox-data"; -import { useItemMap } from "../hooks"; +import { useAppSelector } from "../hooks"; -import AlphaImage from "./AlphaImage"; import ItemGrid from "./ItemGrid"; import PathTattoos from "./PathTattoos"; +import Subsection from "./Subsection"; type Props = { path: PathDef; @@ -17,17 +17,14 @@ type Props = { }; export default function Path({ path, points, items, equipment, tattoos, maxTattooLevel }: Props) { - // Put together a map of item ids to item definitions for this Path - const idToItem = useItemMap([...path.items, ...path.equipment]); + const idToItem = useAppSelector((state) => state.items); return ( - - - - - {path.name} - - {path.maxPoints > 0 && ( + 0 && ( - )} - + ) + } + > {path.items.length > 0 && ( <> Items - + )} {path.equipment.length > 0 && ( @@ -52,7 +50,7 @@ export default function Path({ path, points, items, equipment, tattoos, maxTatto Equipment - + )} {path.tattoos.length > 0 && ( @@ -67,6 +65,6 @@ export default function Path({ path, points, items, equipment, tattoos, maxTatto /> )} - + ); } diff --git a/packages/greenbox-web/src/components/Paths.tsx b/packages/greenbox-web/src/components/Paths.tsx index de53ae3..83334f6 100644 --- a/packages/greenbox-web/src/components/Paths.tsx +++ b/packages/greenbox-web/src/components/Paths.tsx @@ -103,19 +103,17 @@ export default function Paths() { ]} max={max} > - - {paths.map((p) => ( - - ))} - + {paths.map((p) => ( + + ))} ); } diff --git a/packages/greenbox-web/src/components/Section.tsx b/packages/greenbox-web/src/components/Section.tsx index 6cd7662..81db855 100644 --- a/packages/greenbox-web/src/components/Section.tsx +++ b/packages/greenbox-web/src/components/Section.tsx @@ -35,7 +35,9 @@ export default function Section({ title, icon, loading = false, values, max, chi {loading ? : } - {children} + + {children} + ); } diff --git a/packages/greenbox-web/src/components/Skill.tsx b/packages/greenbox-web/src/components/Skill.tsx index 0b5060a..359ea00 100644 --- a/packages/greenbox-web/src/components/Skill.tsx +++ b/packages/greenbox-web/src/components/Skill.tsx @@ -8,20 +8,27 @@ import { PopoverTrigger, Portal, } from "@chakra-ui/react"; -import { getMaxSkillLevel, SkillDef, SkillStatus } from "greenbox-data"; +import { SkillStatus, getMaxSkillLevel } from "greenbox-data"; +import { useAppSelector } from "../hooks"; +import { selectIdToPlayerSkills, selectIdToSkills } from "../store"; import { skillStatusToThingState, skillStatusToTitle } from "../utils"; import SkillDescription from "./SkillDescription"; import Thing from "./Thing"; type Props = { - skill: SkillDef; - status: SkillStatus; - level?: number; + id: number; }; -export default function Skill({ skill, status, level = 0 }: Props) { +export default function Skill({ id }: Props) { + const playerSkills = useAppSelector(selectIdToPlayerSkills); + const skills = useAppSelector(selectIdToSkills); + + const skill = skills[id]; + + const [, status, level] = playerSkills[id] || [id, SkillStatus.NONE, 0]; + return ( diff --git a/packages/greenbox-web/src/components/SkillBucket.tsx b/packages/greenbox-web/src/components/SkillBucket.tsx new file mode 100644 index 0000000..a8b1406 --- /dev/null +++ b/packages/greenbox-web/src/components/SkillBucket.tsx @@ -0,0 +1,22 @@ +import { ClassDef } from "greenbox-data"; + +import { getSkillHeader } from "../utils"; + +import Medal from "./Medal"; +import Subsection from "./Subsection"; + +type Props = React.PropsWithChildren<{ bucket: number; cls: ClassDef; medal: boolean }>; + +export default function SkillBucket({ bucket, cls, medal, children }: Props) { + const [name, image] = getSkillHeader(bucket, cls); + + return ( + } + > + {children} + + ); +} diff --git a/packages/greenbox-web/src/components/SkillClassHeading.tsx b/packages/greenbox-web/src/components/SkillClassHeading.tsx deleted file mode 100644 index 8aa0ddd..0000000 --- a/packages/greenbox-web/src/components/SkillClassHeading.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Box, Heading, HStack } from "@chakra-ui/react"; -import { ClassDef } from "greenbox-data"; - -import { getSkillHeader } from "../utils"; - -import AlphaImage from "./AlphaImage"; -import Medal from "./Medal"; - -type Props = { bucket: number; cls: ClassDef; medal: boolean }; - -export default function SkillClassHeading({ bucket, cls, medal }: Props) { - const [name, image] = getSkillHeader(bucket, cls); - - return ( - - - - {name} - - - {medal && cls && } - - ); -} diff --git a/packages/greenbox-web/src/components/Skills.tsx b/packages/greenbox-web/src/components/Skills.tsx index 1a27745..ff71e0c 100644 --- a/packages/greenbox-web/src/components/Skills.tsx +++ b/packages/greenbox-web/src/components/Skills.tsx @@ -1,20 +1,20 @@ -import { SimpleGrid, Stack } from "@chakra-ui/react"; +import { SimpleGrid } from "@chakra-ui/react"; import { SkillDef, SkillStatus } from "greenbox-data"; import { useMemo } from "react"; import { useAppSelector } from "../hooks"; -import { createPlayerDataSelector } from "../store"; +import { selectIdToPlayerSkills, selectPlayerSkills } from "../store"; import { getSkillBucket } from "../utils"; import MutexSkills from "./MutexSkills"; import Section from "./Section"; import Skill from "./Skill"; -import SkillClassHeading from "./SkillClassHeading"; - -const selectPlayerSkills = createPlayerDataSelector("skills"); +import SkillBucket from "./SkillBucket"; export default function Skills() { const playerSkills = useAppSelector(selectPlayerSkills); + const idToSkill = useAppSelector(selectIdToPlayerSkills); + const allSkills = useAppSelector((state) => state.skills); const skills = useMemo(() => allSkills.filter((s) => s.permable), [allSkills]); const classes = useAppSelector((state) => state.classes); @@ -28,14 +28,7 @@ export default function Skills() { () => playerSkills.filter((s) => s[1] === SkillStatus.SOFTCORE).length, [playerSkills], ); - const idToSkill = useMemo( - () => - playerSkills.reduce( - (acc, s) => ({ ...acc, [s[0]]: s }), - {} as { [id: number]: (typeof playerSkills)[number] }, - ), - [playerSkills], - ); + const idToClass = useMemo( () => classes.reduce( @@ -92,54 +85,45 @@ export default function Skills() { ]} max={skills.length} > - - {bucketedSkills.map(([bucket, contents]) => { - const allHardcorePermed = contents.every( - (s) => idToSkill[s.id]?.[1] === SkillStatus.HARDCORE, - ); + {bucketedSkills.map(([bucket, contents]) => { + const allHardcorePermed = contents.every( + (s) => idToSkill[s.id]?.[1] === SkillStatus.HARDCORE, + ); - return ( - - - - {contents.map((s) => { - switch (s.id) { - case 191: - case 192: - case 193: - skillGroup.push(s); - if (s.id !== 193) return null; + return ( + + + {contents.map((s) => { + switch (s.id) { + case 191: + case 192: + case 193: + skillGroup.push(s); + if (s.id !== 193) return null; - const group = [...skillGroup]; - skillGroup.length = 0; - return ( - idToSkill[s.id]?.[1] ?? SkillStatus.NONE)} - /> - ); - default: - return ( - - ); - } - })} - - - ); - })} - + const group = [...skillGroup]; + skillGroup.length = 0; + return ( + idToSkill[s.id]?.[1] ?? SkillStatus.NONE)} + /> + ); + default: + return ; + } + })} + + + ); + })} ); } diff --git a/packages/greenbox-web/src/components/SlimeTubeSection.tsx b/packages/greenbox-web/src/components/SlimeTubeSection.tsx index e7db7b6..bb8c121 100644 --- a/packages/greenbox-web/src/components/SlimeTubeSection.tsx +++ b/packages/greenbox-web/src/components/SlimeTubeSection.tsx @@ -1,9 +1,25 @@ +import { SimpleGrid } from "@chakra-ui/react"; + import Section from "./Section"; +import Skill from "./Skill"; +import Subsection from "./Subsection"; + +const SKILLS = [ + 46, // Slimy Sinews + 47, // Slimy Synapses + 48, // Slimy Shoulders +]; export default function SlimeTubeSection() { return (
- Coming soon! + + + {SKILLS.map((id) => ( + + ))} + +
); } diff --git a/packages/greenbox-web/src/components/Subsection.tsx b/packages/greenbox-web/src/components/Subsection.tsx new file mode 100644 index 0000000..b3e9c7b --- /dev/null +++ b/packages/greenbox-web/src/components/Subsection.tsx @@ -0,0 +1,24 @@ +import { HStack, Heading, Stack } from "@chakra-ui/react"; + +import AlphaImage from "./AlphaImage"; + +type Props = React.PropsWithChildren<{ + title: string; + image: string; + right?: React.ReactNode; +}>; + +export default function Subsection({ image, title, right = null, children }: Props) { + return ( + + + + + {title} + + {right} + + {children} + + ); +} diff --git a/packages/greenbox-web/src/hooks.ts b/packages/greenbox-web/src/hooks.ts index 6924ba2..195b262 100644 --- a/packages/greenbox-web/src/hooks.ts +++ b/packages/greenbox-web/src/hooks.ts @@ -6,24 +6,3 @@ import type { RootState, AppDispatch } from "./store"; export const useAppDispatch: () => AppDispatch = useDispatch; export const useAppSelector: TypedUseSelectorHook = useSelector; - -export function useItemMap(items: number[]) { - const all = useAppSelector((state) => state.items); - - return useMemo( - () => - items.reduce( - (acc, id) => ({ - ...acc, - // Because items appear in order of id with some gaps, an item of id i can be really quickly found by searching - // backwards from position i in the array. Searching backwards is annoying in JavaScript so I just reduce right - // and truncate the array when I find the item. This is fine because .slice() makes a copy of the state array. - [id]: all - .slice(0, id) - .reduceRight((_1, i, _2, arr) => (i.id === id ? ((arr.length = 0), i) : i)), - }), - {} as { [id: number]: (typeof all)[number] }, - ), - [items, all], - ); -} diff --git a/packages/greenbox-web/src/store/index.ts b/packages/greenbox-web/src/store/index.ts index 6734677..d6eb227 100644 --- a/packages/greenbox-web/src/store/index.ts +++ b/packages/greenbox-web/src/store/index.ts @@ -53,7 +53,7 @@ export interface GreenboxState { effects: EffectDef[]; familiars: FamiliarDef[]; iotms: IotMDef[]; - items: ItemDef[]; + items: { [id: number]: ItemDef }; paths: PathDef[]; skills: SkillDef[]; tattoos: TattooDef[]; @@ -218,7 +218,11 @@ export const greenboxSlice = createSlice({ }) .addCase(fetchItems.fulfilled, (state, action) => { if (action.payload !== null) { - state.items = action.payload.data; + const items = action.payload.data; + state.items = items.reduce( + (acc, i) => ({ ...acc, [i.id]: i }), + {} as { [id: number]: (typeof items)[number] }, + ); state.sizeAtLastFetch.items = action.payload.size; } @@ -344,3 +348,30 @@ export const createPlayerDataSelector = state.playerData?.[key]; return createSelector([selectPlayerData], (data) => data ?? ([] as api.RawSnapshotData[K])); }; + +export const selectPlayerSkills = createPlayerDataSelector("skills"); + +export const selectIdToPlayerSkills = createSelector(selectPlayerSkills, (playerSkills) => + playerSkills.reduce( + (acc, s) => ({ ...acc, [s[0]]: s }), + {} as { [id: number]: (typeof playerSkills)[number] }, + ), +); + +export const selectIdToSkills = createSelector( + (state: RootState) => state.skills, + (skills) => + skills.reduce( + (acc, s) => ({ ...acc, [s.id]: s }), + {} as { [id: number]: (typeof skills)[number] }, + ), +); + +export const selectPlayerItems = createPlayerDataSelector("items"); + +export const selectIdToPlayerItems = createSelector(selectPlayerItems, (playerItems) => + playerItems.reduce( + (acc, s) => ({ ...acc, [s[0]]: s }), + {} as { [id: number]: (typeof playerItems)[number] }, + ), +); diff --git a/packages/greenbox-web/src/utils.ts b/packages/greenbox-web/src/utils.ts index b65329c..3918e0c 100644 --- a/packages/greenbox-web/src/utils.ts +++ b/packages/greenbox-web/src/utils.ts @@ -1,12 +1,8 @@ import { ClassDef, ItemStatus, SkillDef, SkillStatus } from "greenbox-data"; export function itemStatusToThingState(status: ItemStatus) { - switch (status) { - case ItemStatus.HAVE: - return "complete"; - default: - return null; - } + if (status > 0) return "complete"; + return null; } export function skillStatusToThingState(status: SkillStatus) {