From b4d86212a942a637d49d48b5d5754695835b254a Mon Sep 17 00:00:00 2001 From: Christopher Dollard Date: Fri, 26 Jul 2024 00:26:52 -0400 Subject: [PATCH] 0.9.4 - Even more UI updates (#38) --- package.json | 2 +- src/core/models/creature.ts | 30 +- src/index.css | 2 +- src/webui/App.module.scss | 30 +- src/webui/App.tsx | 8 +- src/webui/components/about.module.scss | 18 - .../components/{about.tsx => about/index.tsx} | 6 +- src/webui/components/about/styles.module.scss | 40 +++ src/webui/components/map_preview/index.tsx | 228 +++++++------ src/webui/components/map_preview/plan.tsx | 35 +- .../map_preview/script_preview/index.tsx | 192 +++++++++++ .../script_preview/styles.module.scss | 55 +++ src/webui/components/map_preview/stats.tsx | 239 +++++++------- .../components/map_preview/style.module.scss | 312 +++++++++--------- src/webui/components/map_preview/tiles.tsx | 13 +- tsconfig.json | 2 +- 16 files changed, 773 insertions(+), 439 deletions(-) delete mode 100644 src/webui/components/about.module.scss rename src/webui/components/{about.tsx => about/index.tsx} (86%) create mode 100644 src/webui/components/about/styles.module.scss create mode 100644 src/webui/components/map_preview/script_preview/index.tsx create mode 100644 src/webui/components/map_preview/script_preview/styles.module.scss diff --git a/package.json b/package.json index 9191dc8..685189d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "groundhog", - "version": "0.9.3", + "version": "0.9.4", "homepage": "https://charredutensil.github.io/groundhog", "private": true, "dependencies": { diff --git a/src/core/models/creature.ts b/src/core/models/creature.ts index 03dde66..5611171 100644 --- a/src/core/models/creature.ts +++ b/src/core/models/creature.ts @@ -14,11 +14,31 @@ export class CreatureTemplate { } } -export const ROCK_MONSTER = new CreatureTemplate("CreatureRockMonster_C", "Rock Monster", "Rm"); -export const ICE_MONSTER = new CreatureTemplate("CreatureIceMonster_C", "Ice Monster", "Im"); -export const LAVA_MONSTER = new CreatureTemplate("CreatureLavaMonster_C", "Lava Monster", "Lm"); -export const SLIMY_SLUG = new CreatureTemplate("CreatureSlimySlug_C", "Slimy Slug", "Sg"); -export const SMALL_SPIDER = new CreatureTemplate("CreatureSmallSpider_C", "Small Spider", "Sr"); +export const ROCK_MONSTER = new CreatureTemplate( + "CreatureRockMonster_C", + "Rock Monster", + "Rm", +); +export const ICE_MONSTER = new CreatureTemplate( + "CreatureIceMonster_C", + "Ice Monster", + "Im", +); +export const LAVA_MONSTER = new CreatureTemplate( + "CreatureLavaMonster_C", + "Lava Monster", + "Lm", +); +export const SLIMY_SLUG = new CreatureTemplate( + "CreatureSlimySlug_C", + "Slimy Slug", + "Sg", +); +export const SMALL_SPIDER = new CreatureTemplate( + "CreatureSmallSpider_C", + "Small Spider", + "Sr", +); export const BAT = new CreatureTemplate("CreatureBat_C", "Bat", "Bt"); export function monsterForBiome(biome: Biome): CreatureTemplate { diff --git a/src/index.css b/src/index.css index f4a1093..14d67f0 100644 --- a/src/index.css +++ b/src/index.css @@ -1,4 +1,4 @@ -@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,1,0&family=Russo+One&family=Outfit:wght@500&family=Silkscreen:wght@400;700&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,1,0&family=Russo+One&family=Outfit:wght@500&family=Silkscreen:wght@400;700&family=IBM+Plex+Mono:wght@400&display=swap"); body { margin: 0; diff --git a/src/webui/App.module.scss b/src/webui/App.module.scss index 3f79862..5f902e1 100644 --- a/src/webui/App.module.scss +++ b/src/webui/App.module.scss @@ -11,6 +11,7 @@ --font-body: "Outfit"; --font-cyber: "Russo One"; --font-tiny: "Silkscreen"; + --font-mono: "IBM Plex Mono"; --palette-fg: white; --palette-bg: #121212; @@ -61,21 +62,21 @@ bottom: 0; background: var(--palette-settings-bg); - + &::before { - content: ''; + content: ""; position: absolute; left: 0; top: 0; right: 0; bottom: 0; - background-image: - linear-gradient( - to left, - transparent 0px, - var(--palette-accent) 25px, - transparent 400px); + background-image: linear-gradient( + to left, + transparent 0px, + var(--palette-accent) 25px, + transparent 400px + ); background-size: 800px 100%; animation: scan-scrim 3s infinite linear; opacity: 0; @@ -86,9 +87,9 @@ opacity: 1; transition: opacity 250ms ease-out; } - + &::after { - content: ''; + content: ""; position: absolute; left: 0; top: 0; @@ -97,18 +98,19 @@ background: var(--palette-bg); mask-composite: subtract; - mask-image: - linear-gradient( + mask-image: linear-gradient( to right, white 16px, transparent 16px, transparent 17px, - white 17px), + white 17px + ), linear-gradient( transparent 16px, black 16px, black 17px, - transparent 17px); + transparent 17px + ); mask-size: 32px 32px; } } diff --git a/src/webui/App.tsx b/src/webui/App.tsx index 87b2399..82c5f8b 100644 --- a/src/webui/App.tsx +++ b/src/webui/App.tsx @@ -32,6 +32,7 @@ const MAP_OVERLAY_BUTTONS: readonly { { of: "erosion", label: "Erosion", enabled: (c) => !!c?.erosion }, { of: "oxygen", label: "Oxygen", enabled: (c) => c?.oxygen !== undefined }, { of: "lore", label: "Lore", enabled: (c) => !!c?.lore }, + { of: "script", label: "Script", enabled: (c) => !!c?.script }, { of: "about", label: "About", enabled: (c) => true }, ]; @@ -118,13 +119,16 @@ function App() { (window as any).cavern = state.cavern; }, [state]); + const isLoading = + (autoGenerate && !state.cavern?.serialized) || mapOverlay === "about"; + return (
-
+
{state.cavern && ( )}
- {mapOverlay === "lore" && } {mapOverlay === "about" && } + {mapOverlay === "lore" && }

Show

diff --git a/src/webui/components/about.module.scss b/src/webui/components/about.module.scss deleted file mode 100644 index d61e52c..0000000 --- a/src/webui/components/about.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -.about { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - padding: 48px; - display: flex; - flex-direction: column; - justify-content: center; - background: var(--palette-bg); - color: var(--palette-accent); - font-family: "Outfit"; - - a { - color: var(--palette-fg); - } -} diff --git a/src/webui/components/about.tsx b/src/webui/components/about/index.tsx similarity index 86% rename from src/webui/components/about.tsx rename to src/webui/components/about/index.tsx index 55486a5..27c26fe 100644 --- a/src/webui/components/about.tsx +++ b/src/webui/components/about/index.tsx @@ -1,9 +1,9 @@ import React from "react"; -import styles from "./about.module.scss"; +import styles from "./styles.module.scss"; const About = () => ( -
-
+
+

groundHog v{process.env.REACT_APP_VERSION}

By Christopher Dollard (aka charredUtensil)

diff --git a/src/webui/components/about/styles.module.scss b/src/webui/components/about/styles.module.scss new file mode 100644 index 0000000..ee5b228 --- /dev/null +++ b/src/webui/components/about/styles.module.scss @@ -0,0 +1,40 @@ +.popoverWrapper { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + display: flex; + flex-direction: column; + justify-content: center; + + > div { + background: var(--palette-bg); + border: 1px solid var(--palette-settings-bg); + margin: 0 auto; + padding: 0 24px; + max-width: 500px; + max-height: calc(100% - 100px); + overflow-x: hidden; + overflow-y: scroll; + } + + .about { + color: var(--palette-accent); + + a { + color: var(--palette-fg); + } + } + + .script { + transform: translateX(-250px); + width: 300px; + + .src { + white-space: pre; + font-family: var(--font-mono); + font-size: 12px; + } + } +} diff --git a/src/webui/components/map_preview/index.tsx b/src/webui/components/map_preview/index.tsx index 8ced21b..fe9f743 100644 --- a/src/webui/components/map_preview/index.tsx +++ b/src/webui/components/map_preview/index.tsx @@ -1,4 +1,4 @@ -import React, { CSSProperties, createRef } from "react"; +import React, { CSSProperties, useReducer, useState } from "react"; import { Cavern } from "../../../core/models/cavern"; import BaseplatePreview from "./baseplate"; import PathPreview from "./path"; @@ -11,6 +11,7 @@ import styles from "./style.module.scss"; import HeightPreview from "./height"; import Stats from "./stats"; import PlansPreview from "./plan"; +import ScriptPreview, { ScriptOverlay } from "./script_preview"; export type MapOverlay = | "about" @@ -24,30 +25,21 @@ export type MapOverlay = | "ore" | "overview" | "oxygen" + | "script" | "tiles" | null; -// function getTransform(cavern: Cavern, mapOverlay: MapOverlay) { -// if (mapOverlay !== "overview") { -// return undefined; -// } -// if (!cavern.cameraPosition) { -// return `scale(2) rotate3d(1, 0, 0, 60deg) rotate(-30deg)` -// } -// const { x, y, yaw, pitch } = cavern.cameraPosition; -// return `scale(6) rotate3d(1, 0, 0, ${pitch}rad) rotate(${Math.PI / -2 - yaw}rad) translate(${-x * 6}px, ${-y * 6}px)`; -// } function getTransform(cavern: Cavern, mapOverlay: MapOverlay) { if (mapOverlay !== "overview" || !cavern.cameraPosition) { return {}; } const { x, y, yaw, pitch } = cavern.cameraPosition; return { - '--pvw-scale': 6, - '--pvw-pitch': `${pitch}rad`, - '--pvw-yaw': `${Math.PI - (yaw + Math.PI * 1.5) % (Math.PI * 2)}rad`, - '--pvw-tr-x': -x * 6, - '--pvw-tr-y': -y * 6, + "--pvw-scale": 6, + "--pvw-pitch": `${pitch}rad`, + "--pvw-yaw": `${Math.PI - ((yaw + Math.PI * 1.5) % (Math.PI * 2))}rad`, + "--pvw-tr-x": -x * 6, + "--pvw-tr-y": -y * 6, } as CSSProperties; } @@ -64,99 +56,139 @@ export default function CavernPreview({ showOutlines: boolean; showPearls: boolean; }) { - const holder = createRef(); + const [scriptLineHovered, setScriptLineHovered] = useState(-1); + const [scriptLineOffsets, setScriptLineOffsets] = useReducer( + (was: number[], now: number[]) => { + if (was.length !== now.length) { + return now; + } + for (let i = 0; i < was.length; i++) { + if (was[i] !== now[i]) { + return now; + } + } + return was; + }, + [], + ); + + switch (mapOverlay) { + case "about": + case "lore": + return null; + default: + } + const height = cavern.context.targetSize * 2 * 6; const width = Math.max(height, cavern.context.targetSize * 6 + 600); return (

- - {} - {mapOverlay === "height" && cavern.height && ( - - )} - {showOutlines && - cavern.baseplates?.map((bp) => ( - + {mapOverlay === "script" && ( + + )} +
+ + {} + {mapOverlay === "height" && cavern.height && ( + + )} + {showOutlines && + cavern.baseplates?.map((bp) => ( + + ))} + {showOutlines && + cavern.paths?.map((pa) => )} + {cavern.buildings?.map((b, i) => ( + ))} - {showOutlines && - cavern.paths?.map((pa) => )} - {cavern.buildings?.map((b, i) => ( - - ))} - {cavern.creatures?.map((c) => ( - - ))} - {cavern.miners?.map((m) => ( - - ))} - {cavern.vehicles?.map((v) => ( - - ))} - {mapOverlay === "discovery" && - cavern.openCaveFlags?.map((_, x, y) => ( - + {cavern.creatures?.map((c) => ( + ))} - {showOutlines && cavern.plans && } - {showPearls && - cavern.plans - ?.filter((pl) => "outerPearl" in pl) - .map((pl) => ( - - ))} - {showPearls && - cavern.plans - ?.filter((pl) => "innerPearl" in pl) - .map((pl) => ( - + {cavern.miners?.map((m) => ( + + ))} + {cavern.vehicles?.map((v) => ( + + ))} + {mapOverlay === "discovery" && + cavern.openCaveFlags?.map((_, x, y) => ( + ))} - + {mapOverlay === "script" && ( + + )} + {showOutlines && cavern.plans && ( + + )} + {showPearls && + cavern.plans + ?.filter((pl) => "outerPearl" in pl) + .map((pl) => ( + + ))} + {showPearls && + cavern.plans + ?.filter((pl) => "innerPearl" in pl) + .map((pl) => ( + + ))} + +
{error &&
{error.message}
}
diff --git a/src/webui/components/map_preview/plan.tsx b/src/webui/components/map_preview/plan.tsx index ba9e374..1e76b21 100644 --- a/src/webui/components/map_preview/plan.tsx +++ b/src/webui/components/map_preview/plan.tsx @@ -117,7 +117,13 @@ function hall(plan: Partial) { ); } -export default function PlansPreview({ cavern, mapOverlay }: { cavern: Cavern, mapOverlay: MapOverlay }) { +export default function PlansPreview({ + cavern, + mapOverlay, +}: { + cavern: Cavern; + mapOverlay: MapOverlay; +}) { if (!cavern.plans) { return null; } @@ -152,15 +158,21 @@ export default function PlansPreview({ cavern, mapOverlay }: { cavern: Cavern, m sign: -1 | 1, className: string, ): ReactNode { - if (mapOverlay === 'overview') { - return plans.map(plan => { + if (mapOverlay === "script") { + return null; + } + if (mapOverlay === "overview") { + return plans.map((plan) => { const bps = plan.path.baseplates; if (bps.length <= 1) { const [x, y] = bps[0].center; return ( - + - {'architect' in plan && plan.architect.name} {plan.id} + {"architect" in plan && plan.architect.name} {plan.id} ); @@ -172,15 +184,14 @@ export default function PlansPreview({ cavern, mapOverlay }: { cavern: Cavern, m }) .join(" "); return ( - - + + - {'architect' in plan && plan.architect.name} {plan.id} + {"architect" in plan && plan.architect.name} {plan.id} diff --git a/src/webui/components/map_preview/script_preview/index.tsx b/src/webui/components/map_preview/script_preview/index.tsx new file mode 100644 index 0000000..2511655 --- /dev/null +++ b/src/webui/components/map_preview/script_preview/index.tsx @@ -0,0 +1,192 @@ +import React, { + Fragment, + createRef, + useLayoutEffect, + useMemo, +} from "react"; +import { Cavern } from "../../../../core/models/cavern"; +import styles from "./styles.module.scss"; +import { Point } from "../../../../core/common/geometry"; +import { filterTruthy } from "../../../../core/common/utils"; + +type Statement = { + kind: string; + code: string; + pos?: Point; +}; + +const SCALE = 6; + +function parse(script: string): Statement[] { + return script.split("\n").map((line) => { + if (line.startsWith("#")) { + return { kind: "misc", code: line }; + } + if (!line) { + return { kind: "misc", code: "\u00A0" }; + } + const m = line.match(/(?(^|\())[a-z]+:(?\d+),(?\d+)/); + if (m) { + const kind = m.groups!.prefix === "(" ? "condition" : "event"; + return { + kind: kind, + code: line, + pos: [parseInt(m.groups!.x, 10), parseInt(m.groups!.y)], + }; + } + return { kind: "misc", code: line }; + }); +} + +function getLineOffsets(container: HTMLDivElement) { + const result = []; + const lines = Array.from( + container.getElementsByClassName(styles.line), + ) as HTMLElement[]; + let i = 0; + for (; i < lines.length; i++) { + if (lines[i].offsetTop + lines[i].clientHeight >= container.scrollTop) { + break; + } + } + for (; i < lines.length; i++) { + if (lines[i].offsetTop >= container.scrollTop + container.clientHeight) { + break; + } + result[i] = + lines[i].offsetTop + + lines[i].clientHeight / 2 - + container.clientHeight / 2 - + container.scrollTop; + } + return result; +} + +export default function ScriptPreview({ + cavern, + setScriptLineOffsets, + scriptLineHovered, + setScriptLineHovered, +}: { + cavern: Cavern | undefined; + setScriptLineOffsets: (v: number[]) => void; + scriptLineHovered: number; + setScriptLineHovered: (v: number) => void; +}) { + const ref = createRef(); + useLayoutEffect(() => { + const container = ref.current; + if (!container) { + return; + } + setScriptLineOffsets(getLineOffsets(container)); + }, [ref, setScriptLineOffsets]); + const statements = useMemo( + () => (cavern?.script ? parse(cavern.script) : undefined), + [cavern], + ); + return ( +
setScriptLineOffsets(getLineOffsets(ref.current!))} + > +

Script

+
+ {statements?.map(({ code, pos, kind }, i) => { + const className = filterTruthy([ + styles.line, + scriptLineHovered === i && styles.hovered, + styles[kind], + ]).join(" "); + return ( +
setScriptLineHovered(i) : undefined} + > + {code} +
+ ); + })} +
+
+ ); +} + +export function ScriptOverlay({ + cavern, + scriptLineOffsets, + scriptLineHovered, +}: { + cavern: Cavern | undefined; + scriptLineOffsets: number[]; + scriptLineHovered: number; +}) { + const statements = useMemo( + () => (cavern?.script ? parse(cavern.script) : undefined), + [cavern], + ); + if (!cavern?.script) { + return null; + } + const ox = cavern.left!; + const oy = cavern.top!; + return ( + <> + + {statements!.map(({ kind, pos }, i) => { + if (!pos || scriptLineOffsets[i] === undefined) { + return null; + } + const active = true; + const className = filterTruthy([ + styles.tile, + active ? styles.active : styles.inactive, + scriptLineHovered === i && styles.hovered, + styles[kind], + ]).join(" "); + return ( + + ); + })} + + {parse(cavern.script).map(({ kind, pos }, i) => { + if ( + !pos || + scriptLineOffsets[i] === undefined || + scriptLineHovered !== i + ) { + return null; + } + const className = filterTruthy([ + styles.arrow, + scriptLineHovered === i && styles.hovered, + styles[kind], + ]).join(" "); + const lx = -9999; + const ly = scriptLineOffsets[i]; + const px = (pos[0] + ox + 0.5) * SCALE; + const py = (pos[1] + oy + 0.5) * SCALE; + const bx = px - Math.abs(py - ly) * 0.3; + const d = filterTruthy([ + `M ${px} ${py}`, + `L ${bx} ${ly}`, + `L ${lx} ${ly}`, + ]).join(""); + return ( + + + + + ); + })} + + ); +} diff --git a/src/webui/components/map_preview/script_preview/styles.module.scss b/src/webui/components/map_preview/script_preview/styles.module.scss new file mode 100644 index 0000000..48d185c --- /dev/null +++ b/src/webui/components/map_preview/script_preview/styles.module.scss @@ -0,0 +1,55 @@ +.script { + position: relative; + background: var(--palette-bg); + border-right: 1px solid var(--palette-settings-bg); + width: 450px; + overflow-x: hidden; + overflow-y: scroll; + z-index: 1; + + > h2 { + padding: 0 12px; + } + + .src { + font-family: var(--font-mono); + font-size: 12px; + word-wrap: break-word; + + .line { + color: var(--color-codehlt); + padding: 0 12px; + + &.hovered { + color: black; + background: var(--color-codehlt); + } + } + } +} + +.tiles { + .active.tile { + fill: var(--color-codehlt); + } + + .inactive.tile { + fill: transparent; + } +} + +.arrow { + fill: none; + stroke: var(--color-codehlt); + stroke-width: 2px; +} + +.misc { + --color-codehlt: var(--palette-fg); +} +.condition { + --color-codehlt: yellow; +} +.event { + --color-codehlt: cyan; +} diff --git a/src/webui/components/map_preview/stats.tsx b/src/webui/components/map_preview/stats.tsx index bfd25a4..f02c633 100644 --- a/src/webui/components/map_preview/stats.tsx +++ b/src/webui/components/map_preview/stats.tsx @@ -39,128 +39,121 @@ export default function Stats({ cavern: Cavern; mapOverlay: MapOverlay; }) { - return ( -
- {(() => { - switch (mapOverlay) { - case "overview": - return ( - cavern.briefing?.intro && ( -

- Briefing: {cavern.briefing.intro.replace(/\n/g, "\u00B6")} -

- ) - ); - case "tiles": - return ( -
    -
  • Playable area: {cavern.tiles?.size ?? 0}
  • -
  • - Walls:{" "} - {cavern.tiles?.reduce((r, t) => (t.isWall ? r + 1 : r), 0)} -
  • -
- ); - case "entities": { - return ( -
    -
  • Miners: {cavern.miners?.length ?? 0}
  • -
  • - Buildings: -
  • -
  • - Vehicles: -
  • -
  • - Creatures: -
  • -
- ); - } - case "crystals": - return ( -
    -
  • Total EC: {getTotalCrystals(cavern)}
  • - {cavern.objectives?.crystals ? ( -
  • Goal: {cavern.objectives?.crystals}
  • - ) : null} -
- ); - case "ore": - return ( -
    -
  • Total Ore: {getTotalOre(cavern)}
  • - {cavern.objectives?.ore ? ( -
  • Goal: {cavern.objectives?.ore}
  • - ) : null} -
- ); - case "landslides": { - if (!cavern.landslides?.size) { - return null; - } - const count = cavern.landslides.size; - const avgCooldown = - cavern.landslides.reduce((r, ls) => r + ls.cooldown, 0) / count; - return ( -
    -
  • Landslides: {count}
  • - {avgCooldown && ( -
  • Avg Cooldown: {avgCooldown.toFixed(2)} seconds
  • - )} -
- ); - } - case "erosion": { - const count = cavern.erosion?.size ?? 0; - const avgCooldown = cavern.erosion - ? cavern.erosion.reduce((r, ls) => r + ls.cooldown, 0) / count - : null; - return ( -
    -
  • - Water:{" "} - {cavern.tiles?.reduce( - (r, t) => (t === Tile.WATER ? r + 1 : r), - 0, - )} -
  • -
  • - Lava:{" "} - {cavern.tiles?.reduce( - (r, t) => (t === Tile.LAVA ? r + 1 : r), - 0, - )} -
  • -
  • Erosion: {count}
  • - {avgCooldown && ( -
  • Avg Cooldown: {avgCooldown.toFixed(2)} seconds
  • - )} -
- ); - } - case "height": { - if (!cavern.height) { - return null; - } - return ( -
    -
  • - min:{" "} - {cavern.height.reduce((r, h) => (h < r ? h : r), 0).toFixed()} -
  • -
  • - max:{" "} - {cavern.height.reduce((r, h) => (h > r ? h : r), 0).toFixed()} -
  • -
- ); - } - case "oxygen": { - return

Oxygen: {cavern.oxygen?.join("/") ?? "Infinity"}

; - } + const content = (() => { + switch (mapOverlay) { + case "overview": + return ( + cavern.briefing?.intro && ( +

Briefing: {cavern.briefing.intro.replace(/\n/g, "\u00B6")}

+ ) + ); + case "tiles": + return ( +
    +
  • Playable area: {cavern.tiles?.size ?? 0}
  • +
  • + Walls: {cavern.tiles?.reduce((r, t) => (t.isWall ? r + 1 : r), 0)} +
  • +
+ ); + case "entities": { + return ( +
    +
  • Miners: {cavern.miners?.length ?? 0}
  • +
  • + Buildings: +
  • +
  • + Vehicles: +
  • +
  • + Creatures: +
  • +
+ ); + } + case "crystals": + return ( +
    +
  • Total EC: {getTotalCrystals(cavern)}
  • + {cavern.objectives?.crystals ? ( +
  • Goal: {cavern.objectives?.crystals}
  • + ) : null} +
+ ); + case "ore": + return ( +
    +
  • Total Ore: {getTotalOre(cavern)}
  • + {cavern.objectives?.ore ? ( +
  • Goal: {cavern.objectives?.ore}
  • + ) : null} +
+ ); + case "landslides": { + if (!cavern.landslides?.size) { + return null; } - })()} -
- ); + const count = cavern.landslides.size; + const avgCooldown = + cavern.landslides.reduce((r, ls) => r + ls.cooldown, 0) / count; + return ( +
    +
  • Landslides: {count}
  • + {avgCooldown && ( +
  • Avg Cooldown: {avgCooldown.toFixed(2)} seconds
  • + )} +
+ ); + } + case "erosion": { + const count = cavern.erosion?.size ?? 0; + const avgCooldown = cavern.erosion + ? cavern.erosion.reduce((r, ls) => r + ls.cooldown, 0) / count + : null; + return ( +
    +
  • + Water:{" "} + {cavern.tiles?.reduce( + (r, t) => (t === Tile.WATER ? r + 1 : r), + 0, + )} +
  • +
  • + Lava:{" "} + {cavern.tiles?.reduce((r, t) => (t === Tile.LAVA ? r + 1 : r), 0)} +
  • +
  • Erosion: {count}
  • + {avgCooldown && ( +
  • Avg Cooldown: {avgCooldown.toFixed(2)} seconds
  • + )} +
+ ); + } + case "height": { + if (!cavern.height) { + return null; + } + return ( +
    +
  • + min:{" "} + {cavern.height.reduce((r, h) => (h < r ? h : r), 0).toFixed()} +
  • +
  • + max:{" "} + {cavern.height.reduce((r, h) => (h > r ? h : r), 0).toFixed()} +
  • +
+ ); + } + case "oxygen": { + return

Oxygen: {cavern.oxygen?.join("/") ?? "Infinity"}

; + } + default: + return null; + } + })(); + return content &&
{content}
; } diff --git a/src/webui/components/map_preview/style.module.scss b/src/webui/components/map_preview/style.module.scss index c4c0d1f..41f1ddd 100644 --- a/src/webui/components/map_preview/style.module.scss +++ b/src/webui/components/map_preview/style.module.scss @@ -30,6 +30,8 @@ width: 100%; height: 100%; + display: flex; + --pvw-bpak: #222222; --pvw-bpck: #2d004b; --pvw-bphk: #166921; @@ -74,188 +76,194 @@ --pvw-oxex: #555533; --pvw-oxhc: var(--pvw-tile42); - .map { - position: absolute; - transition: transform 1s linear; - transform: - rotate3d(1, 0, 0, var(--pvw-pitch)) - rotate(var(--pvw-yaw)) - matrix( - var(--pvw-scale), - 0, - 0, - var(--pvw-scale), - calc(var(--pvw-tr-x) * var(--pvw-scale)), - calc(var(--pvw-tr-y) * var(--pvw-scale)) - ); - - .baseplate { - font-family: var(--font-tiny); - font-size: 8px; - - .fg { - fill: white; - } + .mapWrapper { + position: relative; + width: 100%; - &.ambiguousKind .bg { - fill: var(--pvw-bpak); - } + .map { + position: absolute; + transition: transform 500ms linear; + transform: rotate3d(1, 0, 0, var(--pvw-pitch)) rotate(var(--pvw-yaw)) + matrix( + var(--pvw-scale), + 0, + 0, + var(--pvw-scale), + calc(var(--pvw-tr-x) * var(--pvw-scale)), + calc(var(--pvw-tr-y) * var(--pvw-scale)) + ); - &.caveKind .bg { - fill: var(--pvw-bpck); - } + .baseplate { + font-family: var(--font-tiny); + font-size: 8px; - &.hallKind .bg { - fill: var(--pvw-bphk); - } - } + .fg { + fill: white; + } - .path { - stroke-linecap: round; - stroke-dasharray: 2, 3; + &.ambiguousKind .bg { + fill: var(--pvw-bpak); + } - &.ambiguousKind { - --fg-color: white; - --fg-opacity: 0.3; - } + &.caveKind .bg { + fill: var(--pvw-bpck); + } - &.spanningKind { - --fg-color: var(--pvw-pathspan); + &.hallKind .bg { + fill: var(--pvw-bphk); + } } - &.auxiliaryKind { - --fg-color: var(--pvw-pathaux); - } + .path { + stroke-linecap: round; + stroke-dasharray: 2, 3; - & > path { - stroke: var(--fg-color); - stroke-opacity: var(--fg-opacity); - stroke-width: 2px; - } + &.ambiguousKind { + --fg-color: white; + --fg-opacity: 0.3; + } - & > text { - fill: var(--fg-color); - opacity: var(--fg-opacity); - font-family: var(--font-tiny); - font-size: 12px; - } - } + &.spanningKind { + --fg-color: var(--pvw-pathspan); + } - .plan { - --color-bg: #444444; - --color-fg: white; - &.caveKind .bg { - stroke-width: 2px; - fill: none; - } - &.hallKind { - .bg { - stroke-linecap: round; - opacity: 0.4; + &.auxiliaryKind { + --fg-color: var(--pvw-pathaux); } - &.spanningPathKind { - --color-fg: var(--pvw-pathspan); + + & > path { + stroke: var(--fg-color); + stroke-opacity: var(--fg-opacity); + stroke-width: 2px; } - &.auxiliaryPathKind { - --color-fg: var(--pvw-pathaux); + + & > text { + fill: var(--fg-color); + opacity: var(--fg-opacity); + font-family: var(--font-tiny); + font-size: 12px; } } - &.fluid6 { - --color-bg: var(--pvw-tile6); - } - &.fluid11 { - --color-bg: var(--pvw-tile11); - } - &.left .label { - text-anchor: end; - } - &.inline .label { - text-anchor: middle; - font-size: 6px; - } - .label { - fill: var(--color-fg); - font-size: 12px; - } - .pointer { - fill: none; - stroke-width: 1px; - stroke: var(--color-fg); - } - .bg { - stroke: var(--color-bg); - } - } - .pearl { - &.innerPearl { - stroke-width: 1px; - &.caveKind { - --color0: #40ffff; - --color1: #40dddd; - --color2: #40bbbb; - --color3: #409999; + .plan { + --color-bg: #444444; + --color-fg: white; + &.caveKind .bg { + stroke-width: 2px; + fill: none; } &.hallKind { - --color0: #ffff20; - --color1: #dddd20; - --color2: #bbbb20; - --color3: #999920; + .bg { + stroke-linecap: round; + opacity: 0.4; + } + &.spanningPathKind { + --color-fg: var(--pvw-pathspan); + } + &.auxiliaryPathKind { + --color-fg: var(--pvw-pathaux); + } } - } - &.outerPearl { - stroke-width: 0.5px; - --color0: #999999; - --color1: #888888; - --color2: #777777; - --color3: #666666; - } - .layer { - &.layer0 { - stroke: var(--color0); + &.fluid6 { + --color-bg: var(--pvw-tile6); } - &.layer1 { - stroke: var(--color1); + &.fluid11 { + --color-bg: var(--pvw-tile11); } - &.layer2 { - stroke: var(--color2); + &.left .label { + text-anchor: end; } - &.layer3 { - stroke: var(--color3); + &.inline .label { + text-anchor: middle; + font-size: 6px; + } + .label { + fill: var(--color-fg); + font-size: 12px; + } + .pointer { + fill: none; + stroke-width: 1px; + stroke: var(--color-fg); + } + .bg { + stroke: var(--color-bg); } } - } - - .tiles { - shape-rendering: crispEdges; - } - .height { - shape-rendering: crispEdges; - } + .pearl { + &.innerPearl { + stroke-width: 1px; + &.caveKind { + --color0: #40ffff; + --color1: #40dddd; + --color2: #40bbbb; + --color3: #409999; + } + &.hallKind { + --color0: #ffff20; + --color1: #dddd20; + --color2: #bbbb20; + --color3: #999920; + } + } + &.outerPearl { + stroke-width: 0.5px; + --color0: #999999; + --color1: #888888; + --color2: #777777; + --color3: #666666; + } + .layer { + &.layer0 { + stroke: var(--color0); + } + &.layer1 { + stroke: var(--color1); + } + &.layer2 { + stroke: var(--color2); + } + &.layer3 { + stroke: var(--color3); + } + } + } - .entity { - --color-fg: var(--pvw-entfriend); - &.enemy { - --color-fg: var(--pvw-entenemy); + .tiles { + shape-rendering: crispEdges; + .tile { + transition: fill 500ms ease-out; + } } - .marker { - fill: var(--color-fg); - stroke: black; - stroke-width: 0.5px; + + .height { + shape-rendering: crispEdges; } - .label { - fill: black; - font-family: "Outfit"; - font-size: 2px; - text-anchor: middle; - pointer-events: none; + + .entity { + --color-fg: var(--pvw-entfriend); + &.enemy { + --color-fg: var(--pvw-entenemy); + } + .marker { + fill: var(--color-fg); + stroke: black; + stroke-width: 0.5px; + } + .label { + fill: black; + font-family: "Outfit"; + font-size: 2px; + text-anchor: middle; + pointer-events: none; + } } - } - .openCaveFlag { - stroke: red; - fill: white; + .openCaveFlag { + stroke: red; + fill: white; + } } } diff --git a/src/webui/components/map_preview/tiles.tsx b/src/webui/components/map_preview/tiles.tsx index 0ff9c26..77b9387 100644 --- a/src/webui/components/map_preview/tiles.tsx +++ b/src/webui/components/map_preview/tiles.tsx @@ -104,6 +104,8 @@ function getFill( } return t.isWall ? tk(t) : "oxex"; } + case "script": + return dk(t); } return null; } @@ -151,11 +153,7 @@ function getTitle( case "overview": case "tiles": return t.name; - case "about": - case "entities": - case "height": - case "lore": - case null: + default: return null; } } @@ -171,10 +169,7 @@ export default function TilesPreview({ return null; } return ( - + {cavern.tiles.map((t, x, y) => { const fill = getFill(cavern, mapOverlay, t, x, y); if (!fill) { diff --git a/tsconfig.json b/tsconfig.json index 6597d5c..3873b6f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es2016", + "target": "es2018", "jsx": "react", "module": "commonjs", "esModuleInterop": true,