diff --git a/src/core/lore/graphs/completeness.test.ts b/src/core/lore/graphs/completeness.test.ts index 6892a49..750d7f9 100644 --- a/src/core/lore/graphs/completeness.test.ts +++ b/src/core/lore/graphs/completeness.test.ts @@ -8,6 +8,7 @@ import { FOUND_LOST_MINERS, FOUND_SLUG_NEST, } from "./events"; +import { NAME } from "./names"; import ORDERS from "./orders"; import PREMISE from "./premise"; import { SEISMIC_FORESHADOW } from "./seismic"; @@ -61,9 +62,14 @@ const EXPECTED = phraseGraph(({ pg, state, start, end, cut, skip }) => { state("resourceObjective"), ), ) + .then(state('rockBiome', 'iceBiome', 'lavaBiome')) .then(end); }); +test(`Name is complete`, () => { + expectCompletion(NAME, EXPECTED); +}) + test(`Premise is complete`, () => { expectCompletion(PREMISE, EXPECTED); }); diff --git a/src/core/lore/graphs/names.ts b/src/core/lore/graphs/names.ts new file mode 100644 index 0000000..4649852 --- /dev/null +++ b/src/core/lore/graphs/names.ts @@ -0,0 +1,266 @@ +import phraseGraph from "../builder"; +import { State } from "../lore"; + +export const NAME = phraseGraph( + ({ pg, state, start, end, cut, skip }) => { + function f({rock, ice, lava, mid, last}: {rock: string[], ice?: string[], lava?: string[], mid?: string[], last: string[]}) { + start + .then( + state('rockBiome').then(...rock), + ice ? state('iceBiome').then(...ice) : cut, + lava ? state('lavaBiome').then(...lava) : cut, + ) + .then(skip, ...(mid || [])) + .then(...last) + .then(end); + } + + f({ + rock: [ + 'Andesite', + 'Anthracite', + 'Argillite', + ], + ice: [ + 'Arctic', + 'Avalanche', + ], + lava: [ + 'Asbestos', + 'Ashen', + ], + last: [ + 'Abyss', + 'Action', + 'Alley', + 'Attack', + ], + }); + + f({ + rock: [ + 'Basalt', + 'Basanite', + 'Bauxite', + 'Boulder', + 'Breccia', + 'Bullion', + ], + ice: [ + 'Blizzard', + ], + last: [ + 'Balance', + 'Blitz', + 'Breach', + 'Break', + 'Bonanza', + 'Burrow', + ], + }); + + f({ + rock: [ + 'Chalk', + 'Claystone', + 'Core', + 'Crystal', + ], + lava: [ + 'Caldera', + 'Cinder', + ], + last: [ + 'Calamity', + 'Caper', + 'Cavern', + 'Chaos', + 'Conflict', + 'Conundrum', + ], + }); + + f({ + rock: [ + 'Diamond', + 'Diorite', + 'Dolomite', + 'Dunite', + ], + ice: [ + 'Drift', + ], + last: [ + 'Depths', + 'Dash', + 'Descent', + 'Despair', + 'Drive', + ], + }); + + f({ + rock: [ + 'Emerald', + 'Evaporite', + ], + lava: [ + 'Ember', + ], + last: [ + 'Enigma', + 'Eruption', + 'Excavation', + 'Express', + ], + }); + + f({ + rock: [ + 'Fault Line', + 'Fissure', + 'Flint', + ], + ice: [ + 'Frostbite', + 'Frosty', + ], + last: [ + 'Folly', + 'Frenzy', + 'Fury', + ], + }); + + f({ + rock: [ + 'Gneiss', + 'Granite', + 'Gritstone', + 'Gypsum', + ], + ice: [ + 'Glacier', + ], + last: [ + 'Gauntlet', + 'Getaway', + ], + }); + + f({ + rock: [ + 'Lapis Lazuli', + 'Laterite', + 'Lignite', + 'Limestone', + ], + lava: [ + 'Lava', + 'Lava Lake', + ], + last: [ + 'Labyrinth', + 'Lair', + 'Lure', + ], + }); + + f({ + rock: [ + 'Marble', + 'Metamorphic', + 'Mineral', + 'Mudstone', + ], + ice: [ + 'Mammoth', + ], + lava: [ + 'Magma', + 'Mantle', + 'Molten', + ], + mid: [ + 'Mine', + 'Moon', + ], + last: [ + 'Mayhem', + 'Maze', + 'Meltdown', + 'Menace', + 'Mishap', + ], + }); + + f({ + rock: [ + 'Phosphorite', + 'Pumice', + ], + ice: [ + 'Permafrost', + 'Polar', + ], + lava: [ + 'Pyroclastic', + 'Pyrolite', + ], + last: [ + 'Passage', + 'Peril', + 'Pit', + 'Plunge', + 'Puzzle', + ], + }); + + f({ + rock: [ + 'Sandstone', + 'Schist', + 'Sedimentary', + 'Shale', + 'Silica', + 'Silt', + 'Slate', + 'Stalactite', + ], + ice: [ + 'Snowdrift', + 'Subzero', + ], + mid: [ + 'Shaft', + ], + last: [ + 'Scramble', + 'Shock', + 'Showdown', + 'Slide', + ], + }); + + f({ + rock: [ + 'Tuff', + 'Turbidite', + 'Twilight', + ], + ice: [ + 'Taiga', + 'Titanic', + 'Tundra', + ], + mid: [ + 'Tunnel', + ], + last: [ + 'Tempest', + 'Terror', + 'Trouble', + ], + }); + }, +); \ No newline at end of file diff --git a/src/core/lore/lore.ts b/src/core/lore/lore.ts index c8ba56f..e29dde2 100644 --- a/src/core/lore/lore.ts +++ b/src/core/lore/lore.ts @@ -14,6 +14,7 @@ import { FOUND_SLUG_NEST, NOMADS_SETTLED, } from "./graphs/events"; +import { NAME } from "./graphs/names"; import ORDERS from "./graphs/orders"; import PREMISE from "./graphs/premise"; import { SEISMIC_FORESHADOW } from "./graphs/seismic"; @@ -35,6 +36,9 @@ export type State = { readonly hqIsRuin: boolean; readonly treasureCaveOne: boolean; readonly treasureCaveMany: boolean; + readonly rockBiome: boolean; + readonly iceBiome: boolean; + readonly lavaBiome: boolean; }; export type FoundLostMinersState = State & { @@ -60,6 +64,7 @@ enum Die { foundAllLostMiners, nomadsSettled, foundSlugNest, + name, } function floodedWith(cavern: AdjuredCavern): FluidType { @@ -207,6 +212,9 @@ export class Lore { spawnIsNomadsTogether: nomads > 1, treasureCaveOne: treasures === 1, treasureCaveMany: treasures > 1, + rockBiome: cavern.context.biome === 'rock', + iceBiome: cavern.context.biome === 'ice', + lavaBiome: cavern.context.biome === 'lava', }; const enemies = filterTruthy([ @@ -229,6 +237,7 @@ export class Lore { briefings(dice: DiceBox) { return { + name: NAME.generate(dice.lore(Die.name), this.state, this.vars), premise: PREMISE.generate(dice.lore(Die.premise), this.state, this.vars), orders: ORDERS.generate(dice.lore(Die.orders), this.state, this.vars), success: SUCCESS.generate( diff --git a/src/core/transformers/04_ephemera/02_enscribe.ts b/src/core/transformers/04_ephemera/02_enscribe.ts index 35e6f24..d8da614 100644 --- a/src/core/transformers/04_ephemera/02_enscribe.ts +++ b/src/core/transformers/04_ephemera/02_enscribe.ts @@ -2,6 +2,7 @@ import { Lore } from "../../lore/lore"; import { AdjuredCavern } from "./01_adjure"; export type EnscribedCavern = AdjuredCavern & { + fileName: string; lore: Lore; levelName: string; briefing: { @@ -12,7 +13,7 @@ export type EnscribedCavern = AdjuredCavern & { }; export default function enscribe(cavern: AdjuredCavern): EnscribedCavern { - const levelName = (() => { + const fileName = (() => { const seed = cavern.context.seed.toString(16).padStart(8, "0"); return [ "gh", @@ -27,11 +28,14 @@ export default function enscribe(cavern: AdjuredCavern): EnscribedCavern { })(); const lore = new Lore(cavern); - const { premise, orders, success, failure } = lore.briefings(cavern.dice); + const { name, premise, orders, success, failure } = lore.briefings( + cavern.dice, + ); + const levelName = name.text; const briefing = { intro: `${premise.text}\n\n${orders.text}`, success: success.text, failure: failure.text, }; - return { ...cavern, lore, levelName, briefing }; + return { ...cavern, fileName, lore, levelName, briefing }; } diff --git a/src/webui/App.tsx b/src/webui/App.tsx index 9dfe1ca..ae7f46b 100644 --- a/src/webui/App.tsx +++ b/src/webui/App.tsx @@ -156,15 +156,8 @@ function App() { /> )} {mapOverlay === "about" && } - {mapOverlay === "lore" && ( - - )} - {state.error && ( - - )} + {mapOverlay === "lore" && } + {state.error && } {!autoGenerate && state.name && (
{state.name}
)} @@ -183,7 +176,7 @@ function App() { download diff --git a/src/webui/components/context_editor/index.tsx b/src/webui/components/context_editor/index.tsx index e412c2b..e077285 100644 --- a/src/webui/components/context_editor/index.tsx +++ b/src/webui/components/context_editor/index.tsx @@ -8,15 +8,14 @@ import { ArchitectsInput } from "./architects"; const INITIAL_SEED = Date.now() % MAX_PLUS_ONE; function parseSeed(v: string) { - if (v[0] === "#") { - v = v.slice(1); - } - const seed = parseInt(v, 16); + const s = v.replace(/[^0-9a-fA-F]+/g, ""); + const seed = s === "" ? -1 : parseInt(s, 16); return seed >= 0 && seed < MAX_PLUS_ONE ? seed : undefined; } -function unparseSeed(v: number) { - return v.toString(16).padStart(8, "0").toUpperCase(); +function unparseSeed(v: number, split: boolean) { + const s = v.toString(16).padStart(8, "0").toUpperCase(); + return split ? `${s.substring(0, 3)} ${s.substring(3, 6)} ${s.substring(6)}` : s; } const expectedTotalPlans = (contextWithDefaults: CavernContext) => { @@ -73,7 +72,7 @@ export function CavernContextInput({ }, []); useEffect(() => { - window.location.hash = unparseSeed(context.seed); + window.location.hash = unparseSeed(context.seed, false); }, [context.seed]); useEffect( @@ -89,7 +88,7 @@ export function CavernContextInput({ { const seed = parseSeed(ev.target.value); if (seed !== undefined) { diff --git a/src/webui/components/map_preview/stats.tsx b/src/webui/components/map_preview/stats.tsx index 254dc02..0b655d0 100644 --- a/src/webui/components/map_preview/stats.tsx +++ b/src/webui/components/map_preview/stats.tsx @@ -43,9 +43,14 @@ export default function Stats({ switch (mapOverlay) { case "overview": return ( - cavern.briefing?.intro && ( -

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

- ) + <> + {cavern.levelName && ( +

{cavern.fileName} {cavern.levelName}

+ )} + {cavern.briefing?.intro && ( +

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

+ )} + ); case "tiles": return ( @@ -141,6 +146,18 @@ export default function Stats({ min:{" "} {cavern.height.reduce((r, h) => (h < r ? h : r), 0).toFixed()} +
  • + mean:{" "} + {(() => { + let sum = 0; + let count = 0; + cavern.height.forEach((h) => { + sum += h; + count += 1; + }); + return (sum / count).toFixed(1); + })()} +
  • max:{" "} {cavern.height.reduce((r, h) => (h > r ? h : r), 0).toFixed()} diff --git a/src/webui/components/map_preview/style.module.scss b/src/webui/components/map_preview/style.module.scss index 57cdefa..9b452e8 100644 --- a/src/webui/components/map_preview/style.module.scss +++ b/src/webui/components/map_preview/style.module.scss @@ -32,16 +32,21 @@ display: flex; + // Blueprints --pvw-bpak: #222222; --pvw-bpck: #2d004b; --pvw-bphk: #166921; + // Paths --pvw-pathspan: yellow; --pvw-pathaux: #24b136; + // Entites --pvw-entfriend: yellow; --pvw-entenemy: red; + // Default tile overlays --pvw-dfluid: #080022; --pvw-dfloor: #180032; --pvw-dwall: #2d004b; + // Tile overlays indicating numeric qty (ore, ec, landslideness...) --pvw-scale0: #555544; --pvw-scale1: #777744; --pvw-scale2: #999944; @@ -50,6 +55,7 @@ --pvw-scale5: #ffff44; --pvw-scale6: #ffff66; --pvw-scale7: #ffff88; + // Specific tile IDs --pvw-tile1: #2d004b; --pvw-tile2: #180032; --pvw-tile3: #180032; @@ -71,8 +77,10 @@ --pvw-tile61: #180032; --pvw-tile62: #180032; --pvw-tile63: #180032; + // Cavern Discovery --pvw-disco0: #ffff44; --pvw-disco1: #882222; + // Oxygen --pvw-oxex: #555533; --pvw-oxhc: var(--pvw-tile42); @@ -277,13 +285,22 @@ bottom: 0; color: var(--palette-accent); background: var(--palette-bg); - font-family: var(--font-tiny); - font-size: 8px; - max-width: 400px; + font-family: var(--font-body); + font-size: 12px; + max-width: calc(100% - 200px); border: 1px solid var(--palette-settings-bg); padding: 4px 14px; margin: 16px; + > * { + margin: 4px 0; + } + + h1 { + font-family: var(--font-cyber); + font-size: 20px; + } + > ul { padding: 0; diff --git a/src/webui/components/popovers/lore.tsx b/src/webui/components/popovers/lore.tsx index e87ffd0..5b49289 100644 --- a/src/webui/components/popovers/lore.tsx +++ b/src/webui/components/popovers/lore.tsx @@ -5,23 +5,15 @@ import { Cavern } from "../../../core/models/cavern"; const disp = (value: string | undefined) => value?.split("\n").flatMap((s, i) => (i > 0 ? [
    , s] : [s])); -export default function LorePreview({ - briefing, - script, -}: Pick) { - const scriptStrings = script - ?.split("\n") - .map((s) => - s.match( - /^string\s+(?[a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*"(?.*)"$/, - ), - ); +export default function LorePreview({ fileName, levelName, briefing, script }: Pick) { + const scriptStrings = script?.split('\n').map(s => s.match(/^string\s+(?[a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*"(?.*)"$/)); if (!(briefing || script)) { return null; } return (
    +

    {levelName} {fileName}

    Briefing

    Introduction

    {disp(briefing?.intro)}

    diff --git a/src/webui/components/popovers/styles.module.scss b/src/webui/components/popovers/styles.module.scss index 6dd8d6c..6a57b0a 100644 --- a/src/webui/components/popovers/styles.module.scss +++ b/src/webui/components/popovers/styles.module.scss @@ -12,18 +12,30 @@ background: var(--palette-bg); border: 1px solid var(--palette-settings-bg); margin: 0 auto; - padding: 0 24px; + padding: 12px 24px; max-width: 500px; max-height: calc(100% - 100px); overflow-x: hidden; overflow-y: scroll; + h1, + h2, + h3, + p { + margin: 4px 0; + } + + h1, h2, h3, a { color: var(--palette-accent); } + .subhead { + font-size: 0.5em; + } + h3::before { font-family: "Material Symbols Outlined"; content: "subdirectory_arrow_right";