diff --git a/src/core/architects/build_and_power.ts b/src/core/architects/build_and_power.ts index d5075ba..1389a49 100644 --- a/src/core/architects/build_and_power.ts +++ b/src/core/architects/build_and_power.ts @@ -1,4 +1,8 @@ -import { BUILD_POWER_GC_FIRST, BUILD_POWER_GC_LAST, BUILD_POWER_GC_PENULTIMATE } from "../lore/graphs/build_and_power"; +import { + BUILD_POWER_GC_FIRST, + BUILD_POWER_GC_LAST, + BUILD_POWER_GC_PENULTIMATE, +} from "../lore/graphs/build_and_power"; import { LoreDie, spellNumber } from "../lore/lore"; import { Architect } from "../models/architect"; import { Building, GEOLOGICAL_CENTER } from "../models/building"; @@ -7,12 +11,19 @@ import { OrderedOrEstablishedPlan } from "../transformers/01_planning/05_establi import { DefaultCaveArchitect, PartialArchitect } from "./default"; import { intersectsOnly } from "./utils/intersects"; import { Rough, mkRough } from "./utils/rough"; -import { declareStringFromLore, eventChain, eventChainSynchronized, mkVars, scriptFragment, transformPoint } from "./utils/script"; +import { + declareStringFromLore, + eventChain, + eventChainSynchronized, + mkVars, + scriptFragment, + transformPoint, +} from "./utils/script"; -const TAG = 'buildAndPower' as const; +const TAG = "buildAndPower" as const; export type BuildAndPowerMetadata = { readonly tag: typeof TAG; - readonly template: Building['template']; + readonly template: Building["template"]; }; const BASE: PartialArchitect = { @@ -21,42 +32,62 @@ const BASE: PartialArchitect = { }; function buildAndPower( - template: Building['template'], - minLevel: Building['level'] = 1, -): Pick, "prime" | "objectives" | "script" | "scriptGlobals"> { - const g = mkVars( - `gBp${template.inspectAbbrev}`, - [ 'onBuild', 'built', 'onPower', 'doneCount', 'done', 'msgA', 'msgB', 'msgC', 'onComplete' ] - ); + template: Building["template"], + minLevel: Building["level"] = 1, +): Pick< + Architect, + "prime" | "objectives" | "script" | "scriptGlobals" +> { + const g = mkVars(`gBp${template.inspectAbbrev}`, [ + "onBuild", + "built", + "onPower", + "doneCount", + "done", + "msgA", + "msgB", + "msgC", + "onComplete", + ]); const metadata: BuildAndPowerMetadata = { tag: TAG, template }; - const mv = (plan: Plan) => mkVars( - `p${plan.id}Bp${template.inspectAbbrev}`, - ['arrow', 'building', 'onInit', 'onBuild'], - ); + const mv = (plan: Plan) => + mkVars(`p${plan.id}Bp${template.inspectAbbrev}`, [ + "arrow", + "building", + "onInit", + "onBuild", + ]); return { prime: () => metadata, - objectives({cavern}) { - const count = cavern.plans - .filter(plan => plan.metadata?.tag === TAG && plan.metadata.template === template).length; + objectives({ cavern }) { + const count = cavern.plans.filter( + (plan) => + plan.metadata?.tag === TAG && plan.metadata.template === template, + ).length; return { - variables: [{ - condition: `${g.done}>0`, - description: [ - 'Build and power a ', - minLevel > 1 ? `Level ${minLevel} ` : '', - template.name, - ' in ', - count > 1 ? 'each' : 'the', - ' marked cave.', - ].join('') - }], + variables: [ + { + condition: `${g.done}>0`, + description: [ + "Build and power a ", + minLevel > 1 ? `Level ${minLevel} ` : "", + template.name, + " in ", + count > 1 ? "each" : "the", + " marked cave.", + ].join(""), + }, + ], sufficient: true, - } + }; }, - scriptGlobals({cavern}) { + scriptGlobals({ cavern }) { const pvs = cavern.plans - .filter(plan => plan.metadata?.tag === TAG && plan.metadata.template === template) - .map(plan => mv(plan)); + .filter( + (plan) => + plan.metadata?.tag === TAG && plan.metadata.template === template, + ) + .map((plan) => mv(plan)); return scriptFragment( `# Globals: Build and Power ${template.name}`, @@ -65,65 +96,88 @@ function buildAndPower( // have collisions. In theory, it shouldn't be possible to level up // multiple buildings at the same time. `building ${g.built}`, - `when(${template.id}.${minLevel > 1 ? 'levelup' : 'new'})[${g.onBuild}]`, + `when(${template.id}.${minLevel > 1 ? "levelup" : "new"})[${g.onBuild}]`, eventChain( g.onBuild, `savebuilding:${g.built};`, minLevel > 1 && `((${g.built}.level<${minLevel}))return;`, - ...pvs.map(v => `${v.onBuild};` satisfies `${string};`), + ...pvs.map((v) => `${v.onBuild};` satisfies `${string};`), ), // Second trigger: power state changes. `int ${g.doneCount}=0`, - ...pvs.map(v => `arrow ${v.arrow}`), - ...pvs.map(v => `building ${v.building}`), + ...pvs.map((v) => `arrow ${v.arrow}`), + ...pvs.map((v) => `building ${v.building}`), `when(${template.id}.poweron)[${g.onPower}]`, `when(${template.id}.poweroff)[${g.onPower}]`, eventChainSynchronized( g.onPower, `${g.doneCount}=0;`, - ...pvs.flatMap(v => [ - `((${v.building}.powered>0))[hidearrow:${v.arrow}][showarrow:${v.building}.row,${v.building}.column,${v.arrow}];`, - `((${v.building}.powered>0))${g.doneCount}+=1;`, - ] satisfies `${string};`[]), + ...pvs.flatMap( + (v) => + [ + `((${v.building}.powered>0))[hidearrow:${v.arrow}][showarrow:${v.building}.row,${v.building}.column,${v.arrow}];`, + `((${v.building}.powered>0))${g.doneCount}+=1;`, + ] satisfies `${string};`[], + ), ), // Messages & done trigger - pvs.length > 1 && scriptFragment( - declareStringFromLore(cavern, LoreDie.buildAndPower, g.msgA, BUILD_POWER_GC_FIRST, {}, { - buildingName: template.name, - remainingCount: spellNumber(pvs.length - 1)}), - `if(${g.doneCount}==1)[msg:${g.msgA}]`, - ), - pvs.length > 2 && scriptFragment( - declareStringFromLore(cavern, LoreDie.buildAndPower, g.msgB, BUILD_POWER_GC_PENULTIMATE, {}, { + pvs.length > 1 && + scriptFragment( + declareStringFromLore( + cavern, + LoreDie.buildAndPower, + g.msgA, + BUILD_POWER_GC_FIRST, + {}, + { + buildingName: template.name, + remainingCount: spellNumber(pvs.length - 1), + }, + ), + `if(${g.doneCount}==1)[msg:${g.msgA}]`, + ), + pvs.length > 2 && + scriptFragment( + declareStringFromLore( + cavern, + LoreDie.buildAndPower, + g.msgB, + BUILD_POWER_GC_PENULTIMATE, + {}, + { + buildingName: template.name, + }, + ), + `if(${g.doneCount}==${pvs.length - 1})[msg:${g.msgB}]`, + ), + `int ${g.done}=0`, + declareStringFromLore( + cavern, + LoreDie.buildAndPower, + g.msgC, + BUILD_POWER_GC_LAST, + {}, + { buildingName: template.name, - }), - `if(${g.doneCount}==${pvs.length - 1})[msg:${g.msgB}]`, + }, ), - `int ${g.done}=0`, - declareStringFromLore(cavern, LoreDie.buildAndPower, g.msgC, BUILD_POWER_GC_LAST, {}, { - buildingName: template.name, - }), `if(${g.doneCount}>=${pvs.length})[${g.onComplete}]`, - eventChain( - g.onComplete, - `msg:${g.msgC};`, - 'wait:2;', - `${g.done}=1;`, - ), + eventChain(g.onComplete, `msg:${g.msgC};`, "wait:2;", `${g.done}=1;`), ); }, - script({cavern, plan}) { + script({ cavern, plan }) { const v = mv(plan); if (plan.path.baseplates.length > 1) { - throw new Error('Plan must have one baseplate.'); + throw new Error("Plan must have one baseplate."); } const bp = plan.path.baseplates[0]; - const arrowPos = plan.innerPearl.flatMap(ly => ly) - .find(pos => cavern.tiles.get(...pos)?.isWall === false); + const arrowPos = plan.innerPearl + .flatMap((ly) => ly) + .find((pos) => cavern.tiles.get(...pos)?.isWall === false); if (!arrowPos) { - throw new Error('No non-wall points found'); + throw new Error("No non-wall points found"); } const atp = transformPoint(cavern, arrowPos); const openOnSpawn = cavern.discoveryZones.get(...arrowPos)!.openOnSpawn; @@ -131,10 +185,7 @@ function buildAndPower( return scriptFragment( `# P${plan.id}: Build and Power ${template.name}`, `if(${openOnSpawn ? `time:0` : `change:${atp}`})[${v.onInit}]`, - eventChain( - v.onInit, - `showarrow:${atp},${v.arrow};`, - ), + eventChain(v.onInit, `showarrow:${atp},${v.arrow};`), eventChain( v.onBuild, // Filter out buildings outside the baseplate rectangle @@ -146,13 +197,13 @@ function buildAndPower( `${g.onPower};`, ), ); - } - } + }, + }; } function bidHelper( plans: readonly OrderedOrEstablishedPlan[], - template: Building['template'], + template: Building["template"], max: number, dormant: number, active: number, @@ -160,7 +211,7 @@ function bidHelper( let extantCount = 0; let unestablishedCount = 0; for (const p of plans) { - if (p.kind === 'cave' && !p.architect) { + if (p.kind === "cave" && !p.architect) { unestablishedCount++; } else if (p.metadata?.tag === TAG) { if (p.metadata.template !== template) { @@ -186,35 +237,38 @@ export const BUILD_AND_POWER = [ ...buildAndPower(GEOLOGICAL_CENTER, 5), ...mkRough( { of: Rough.FLOOR, width: 2, grow: 1 }, - { of: Rough.MIX_DIRT_LOOSE_ROCK, grow: 1, }, + { of: Rough.MIX_DIRT_LOOSE_ROCK, grow: 1 }, { of: Rough.MIX_LOOSE_HARD_ROCK, grow: 0.5 }, { of: Rough.VOID, width: 0, grow: 0.5 }, ), placeBuildings({ cavern, plan, openCaveFlags }) { - plan.innerPearl.some(ly => ly.some(pos => { - if (cavern.tiles.get(...pos)?.isWall === false) { - openCaveFlags.set(...pos, true); - return true; - } - return false; - })) + plan.innerPearl.some((ly) => + ly.some((pos) => { + if (cavern.tiles.get(...pos)?.isWall === false) { + openCaveFlags.set(...pos, true); + return true; + } + return false; + }), + ); return {}; }, - caveBid: ({ cavern, plans, plan, hops }) => - { - const amd = plans[cavern.anchor].metadata; - return !plan.fluid && - plan.pearlRadius > 2 && - plan.pearlRadius < 10 && - plan.path.baseplates.length === 1 && - !(amd?.tag === 'hq' && amd.fixedComplete) && - intersectsOnly(plans, plan, null) && - hops.length > 5 && - !hops.some(h => { - const m = plans[h].metadata; - return m?.tag === TAG && m.template === GEOLOGICAL_CENTER; - }) && - bidHelper(plans, GEOLOGICAL_CENTER, 3, 0.04, 10); - }, - } -] as const satisfies readonly Architect[] \ No newline at end of file + caveBid: ({ cavern, plans, plan, hops }) => { + const amd = plans[cavern.anchor].metadata; + return ( + !plan.fluid && + plan.pearlRadius > 2 && + plan.pearlRadius < 10 && + plan.path.baseplates.length === 1 && + !(amd?.tag === "hq" && amd.fixedComplete) && + intersectsOnly(plans, plan, null) && + hops.length > 5 && + !hops.some((h) => { + const m = plans[h].metadata; + return m?.tag === TAG && m.template === GEOLOGICAL_CENTER; + }) && + bidHelper(plans, GEOLOGICAL_CENTER, 3, 0.04, 10) + ); + }, + }, +] as const satisfies readonly Architect[]; diff --git a/src/core/architects/established_hq/fixed_complete.ts b/src/core/architects/established_hq/fixed_complete.ts index 7c126a6..89b1565 100644 --- a/src/core/architects/established_hq/fixed_complete.ts +++ b/src/core/architects/established_hq/fixed_complete.ts @@ -74,7 +74,14 @@ export const FC_BASE: Pick< // nobody wants that. ...ALL_BUILDINGS.map((bt) => `disable:${bt.id};` as `${string};`), ), - declareStringFromLore(cavern, LoreDie.failureBaseDestroyed, gFCHQ.msgBaseDestroyed, FAILURE_BASE_DESTROYED, {}, {}), + declareStringFromLore( + cavern, + LoreDie.failureBaseDestroyed, + gFCHQ.msgBaseDestroyed, + FAILURE_BASE_DESTROYED, + {}, + {}, + ), `int ${gFCHQ.wasBaseDestroyed}=0`, `if(${TOOL_STORE.id}<=0)[${gFCHQ.onBaseDestroyed}]`, `if(${POWER_STATION.id}<=0)[${gFCHQ.onBaseDestroyed}]`, diff --git a/src/core/architects/established_hq/lost.ts b/src/core/architects/established_hq/lost.ts index 280d08b..292eeab 100644 --- a/src/core/architects/established_hq/lost.ts +++ b/src/core/architects/established_hq/lost.ts @@ -54,7 +54,15 @@ const LOST_BASE: Pick< return scriptFragment( `# P${plan.id}: Lost HQ`, - shouldPanMessage && declareStringFromLore(cavern, LoreDie.foundHq, v.messageDiscover, FOUND_HQ, {}, {}), + shouldPanMessage && + declareStringFromLore( + cavern, + LoreDie.foundHq, + v.messageDiscover, + FOUND_HQ, + {}, + {}, + ), `if(change:${transformPoint(cavern, discoPoint)})[${v.onDiscover}]`, eventChain( v.onDiscover, diff --git a/src/core/architects/fissure.ts b/src/core/architects/fissure.ts index e477c8c..0ee1208 100644 --- a/src/core/architects/fissure.ts +++ b/src/core/architects/fissure.ts @@ -58,7 +58,14 @@ const BASE: PartialArchitect = { return scriptFragment( `# P${plan.id}: Fissure`, `int ${v.tripCount}=0`, - declareStringFromLore(cavern, rng, v.msgForeshadow, SEISMIC_FORESHADOW, {}, {}), + declareStringFromLore( + cavern, + rng, + v.msgForeshadow, + SEISMIC_FORESHADOW, + {}, + {}, + ), ...discoveryPoints.map( (pos) => `if(change:${transformPoint(cavern, pos)})[${v.onTrip}]`, ), @@ -67,7 +74,7 @@ const BASE: PartialArchitect = { ), eventChain( v.onTrip, - `((${v.tripCount}==${trips-1}))[${v.tripCount}+=1][return];`, + `((${v.tripCount}==${trips - 1}))[${v.tripCount}+=1][return];`, `wait:random(5)(30);`, `shake:1;`, `msg:${v.msgForeshadow};`, diff --git a/src/core/architects/lost_miners.ts b/src/core/architects/lost_miners.ts index 39ad8d2..63451a4 100644 --- a/src/core/architects/lost_miners.ts +++ b/src/core/architects/lost_miners.ts @@ -33,7 +33,11 @@ import { } from "./utils/script"; import { EnscribedCavern } from "../transformers/04_ephemera/02_enscribe"; import { LoreDie, spellNumber } from "../lore/lore"; -import { FOUND_ALL_LOST_MINERS, FOUND_LM_BREADCRUMB, FOUND_LOST_MINERS } from "../lore/graphs/events"; +import { + FOUND_ALL_LOST_MINERS, + FOUND_LM_BREADCRUMB, + FOUND_LOST_MINERS, +} from "../lore/graphs/events"; export type LostMinersMetadata = { readonly tag: "lostMiners"; @@ -246,7 +250,14 @@ const BASE: PartialArchitect = { `# Globals: Lost Miners`, `int ${gLostMiners.remainingCaves}=${lostMinerCaves}`, `int ${gLostMiners.done}=0`, - declareStringFromLore(cavern, LoreDie.foundAllLostMiners, gLostMiners.messageFoundAll, FOUND_ALL_LOST_MINERS, {}, {}), + declareStringFromLore( + cavern, + LoreDie.foundAllLostMiners, + gLostMiners.messageFoundAll, + FOUND_ALL_LOST_MINERS, + {}, + {}, + ), eventChain( gLostMiners.onFoundAll, `msg:${gLostMiners.messageFoundAll};`, @@ -284,16 +295,20 @@ const BASE: PartialArchitect = { return scriptFragment( `# P${plan.id}: Lost Miners`, - shouldMessageOnMiners && declareStringFromLore( - cavern, rng, v.msgFoundMiners, FOUND_LOST_MINERS, - { - foundMinersOne: plan.metadata.minersCount <= 1, - foundMinersTogether: plan.metadata.minersCount > 1, - }, - { - foundMinersCount: spellNumber(plan.metadata.minersCount), - }, - ), + shouldMessageOnMiners && + declareStringFromLore( + cavern, + rng, + v.msgFoundMiners, + FOUND_LOST_MINERS, + { + foundMinersOne: plan.metadata.minersCount <= 1, + foundMinersTogether: plan.metadata.minersCount > 1, + }, + { + foundMinersCount: spellNumber(plan.metadata.minersCount), + }, + ), `int ${v.wasFound}=0`, `if(change:${transformPoint(cavern, minersPoint)})[${v.onFoundMiners}]`, eventChain( @@ -309,9 +324,16 @@ const BASE: PartialArchitect = { ), shouldPanMessageOnBreadcrumb && scriptFragment( - declareStringFromLore(cavern, rng, v.msgFoundBreadcrumb, FOUND_LM_BREADCRUMB, {}, { - vehicleName: breadcrumb!.template.name, - }), + declareStringFromLore( + cavern, + rng, + v.msgFoundBreadcrumb, + FOUND_LM_BREADCRUMB, + {}, + { + vehicleName: breadcrumb!.template.name, + }, + ), `if(change:${transformPoint(cavern, breadcrumbPoint)})[${v.onFoundBreadcrumb}]`, eventChain( v.onFoundBreadcrumb, diff --git a/src/core/architects/nomads.ts b/src/core/architects/nomads.ts index 383ac19..e8d29ae 100644 --- a/src/core/architects/nomads.ts +++ b/src/core/architects/nomads.ts @@ -179,7 +179,14 @@ const BASE: PartialArchitect = { // Acknowledge the construction of a Support Station. return scriptFragment( "# Globals: Nomads, no HQ", - declareStringFromLore(cavern, LoreDie.nomadsSettled, gNomads.messageBuiltBase, NOMADS_SETTLED, {}, {}), + declareStringFromLore( + cavern, + LoreDie.nomadsSettled, + gNomads.messageBuiltBase, + NOMADS_SETTLED, + {}, + {}, + ), `if(${SUPPORT_STATION.id}.new)[${gNomads.onBuiltBase}]`, eventChain(gNomads.onBuiltBase, `msg:${gNomads.messageBuiltBase};`), ); diff --git a/src/core/architects/slugs.ts b/src/core/architects/slugs.ts index b9130ac..75893f7 100644 --- a/src/core/architects/slugs.ts +++ b/src/core/architects/slugs.ts @@ -74,7 +74,14 @@ const SLUG_NEST: PartialArchitect = { return scriptFragment( `# P${plan.id}: Slug Nest`, - declareStringFromLore(cavern, LoreDie.foundSlugNest, v.messageDiscover, FOUND_SLUG_NEST, {}, {}), + declareStringFromLore( + cavern, + LoreDie.foundSlugNest, + v.messageDiscover, + FOUND_SLUG_NEST, + {}, + {}, + ), `if(change:${transformPoint(cavern, discoPoint)})[${v.onDiscover}]`, eventChain( v.onDiscover, diff --git a/src/core/architects/treasure.ts b/src/core/architects/treasure.ts index c2021a4..141e5f2 100644 --- a/src/core/architects/treasure.ts +++ b/src/core/architects/treasure.ts @@ -83,7 +83,14 @@ const HOARD: typeof BASE = { return scriptFragment( "# Globals: Hoard", `bool ${g.wasTriggered}=false`, - declareStringFromLore(cavern, LoreDie.foundHoard, g.message, FOUND_HOARD, {}, {}), + declareStringFromLore( + cavern, + LoreDie.foundHoard, + g.message, + FOUND_HOARD, + {}, + {}, + ), `int ${g.crystalsAvailable}=0`, ); }, diff --git a/src/core/architects/utils/creature_spawners.ts b/src/core/architects/utils/creature_spawners.ts index 9ec55c6..258e89a 100644 --- a/src/core/architects/utils/creature_spawners.ts +++ b/src/core/architects/utils/creature_spawners.ts @@ -182,7 +182,8 @@ function creatureSpawnScript( armEvent, opts.initialCooldown && `wait:random(${opts.initialCooldown.min.toFixed(2)})(${opts.initialCooldown.max.toFixed(2)});`, - armTriggers.length !== 1 && `((${v.state}>${STATE.INITIAL}))[return][${v.state}=${STATE.ARMED}];`, + armTriggers.length !== 1 && + `((${v.state}>${STATE.INITIAL}))[return][${v.state}=${STATE.ARMED}];`, opts.triggerOnFirstArmed && `${v.doSpawn};`, ), @@ -229,15 +230,17 @@ function creatureSpawnScript( // Hoard mode must be "manually" re-armed by a monster visiting the hoard // within cooldown. - !once && opts.retriggerMode === "hoard" && scriptFragment( - ...plan.innerPearl[0].map( - (point) => - `when(enter:${transformPoint(cavern, point)},${opts.creature.id})[${v.doRetrigger}]`, - ), - eventChain( - v.doRetrigger, - `((${v.state}==${STATE.AWAITING_REARM}))${v.state}=${STATE.COOLDOWN};`, + !once && + opts.retriggerMode === "hoard" && + scriptFragment( + ...plan.innerPearl[0].map( + (point) => + `when(enter:${transformPoint(cavern, point)},${opts.creature.id})[${v.doRetrigger}]`, + ), + eventChain( + v.doRetrigger, + `((${v.state}==${STATE.AWAITING_REARM}))${v.state}=${STATE.COOLDOWN};`, + ), ), - ), ); } diff --git a/src/core/architects/utils/script.ts b/src/core/architects/utils/script.ts index 0ab3055..4d1eae7 100644 --- a/src/core/architects/utils/script.ts +++ b/src/core/architects/utils/script.ts @@ -1,4 +1,3 @@ - import { Point } from "../../common/geometry"; import { PseudorandomStream } from "../../common/prng"; import { FormatVars, PhraseGraph } from "../../lore/builder"; @@ -36,25 +35,17 @@ export function eventChain(name: string, ...rest: (`${string};` | Falsy)[]) { return `${name}::;\n${scriptFragment(...rest)}\n`; } -export function eventChainSynchronized(name: string, ...rest: (`${string};` | Falsy)[]) { +export function eventChainSynchronized( + name: string, + ...rest: (`${string};` | Falsy)[] +) { const semaphore = `${name}_lock`; return scriptFragment( `int ${semaphore}=0`, - eventChain( - name, - `((${semaphore}==0))[${semaphore}=1][${name}_wait];` - ), + eventChain(name, `((${semaphore}==0))[${semaphore}=1][${name}_wait];`), `when(${semaphore}==1)[${name}_syn]`, - eventChain( - `${name}_syn`, - ...rest, - `${semaphore}=0;`, - ), - eventChain( - `${name}_wait`, - 'wait:1;', - `${name};`, - ), + eventChain(`${name}_syn`, ...rest, `${semaphore}=0;`), + eventChain(`${name}_wait`, "wait:1;", `${name};`), ); } @@ -62,7 +53,7 @@ export function escapeString(s: string) { return s.replace(/\\/g, "").replace(/"/g, '\\"'); } -export function declareStringFromLore( +export function declareStringFromLore( cavern: EnscribedCavern, die: PseudorandomStream | LoreDie, varName: string, @@ -70,13 +61,14 @@ export function declareStringFromLore( state: T, formatVars: FormatVars, ) { - const rng: PseudorandomStream = (die instanceof PseudorandomStream ? die : cavern.dice.lore(die)); + const rng: PseudorandomStream = + die instanceof PseudorandomStream ? die : cavern.dice.lore(die); const val = pg.generate( rng, - {...cavern.lore.state, ...state}, - {...cavern.lore.formatVars, ...formatVars} + { ...cavern.lore.state, ...state }, + { ...cavern.lore.formatVars, ...formatVars }, ).text; - return `string ${varName}="${escapeString(val)}"` + return `string ${varName}="${escapeString(val)}"`; } export enum DzPriorities { diff --git a/src/core/lore/graphs/build_and_power.ts b/src/core/lore/graphs/build_and_power.ts index 9f730e6..a60abbd 100644 --- a/src/core/lore/graphs/build_and_power.ts +++ b/src/core/lore/graphs/build_and_power.ts @@ -20,19 +20,11 @@ export const BUILD_POWER_GC_FIRST = phraseGraph( ); export const BUILD_POWER_GC_PENULTIMATE = phraseGraph( ({ pg, state, start, end, cut, skip }) => { - start - .then( - "Just one more!", - ) - .then(end); + start.then("Just one more!").then(end); }, ); export const BUILD_POWER_GC_LAST = phraseGraph( ({ pg, state, start, end, cut, skip }) => { - start - .then( - "Well done!", - ) - .then(end); + start.then("Well done!").then(end); }, -); \ No newline at end of file +); diff --git a/src/core/lore/graphs/completeness.test.ts b/src/core/lore/graphs/completeness.test.ts index 1e5a8a0..d064f46 100644 --- a/src/core/lore/graphs/completeness.test.ts +++ b/src/core/lore/graphs/completeness.test.ts @@ -65,6 +65,7 @@ const EXPECTED = phraseGraph(({ pg, state, start, end, cut, skip }) => { ), ) .then(state("rockBiome", "iceBiome", "lavaBiome")) + .then(skip, state("hasGiantCave")) .then(end); }); diff --git a/src/core/lore/graphs/orders.ts b/src/core/lore/graphs/orders.ts index e1421a5..d66c90b 100644 --- a/src/core/lore/graphs/orders.ts +++ b/src/core/lore/graphs/orders.ts @@ -88,15 +88,34 @@ const ORDERS = phraseGraph(({ pg, state, start, end, cut, skip }) => { ), ) .then( - collect_resources, - pg( - "continue mining operations.", - "explore the cavern.", - "resume our mining operation.", - "search the cavern.", - ) - .then(we_need) - .then(cut), + skip, + collect_resources.then(cut), + state("buildAndPowerGcOne") + .then("construct a Geological Center in the marked cavern") + .then( + pg(", upgrade it to Level 5, and keep it powered on.").then( + end, + pg("Finally, ").then(collect_resources).then(cut), + pg("Then,"), + ), + ), + state("buildAndPowerGcMultiple") + .then( + "construct a Geological Center in each of the marked caverns", + "build a Geological Center in each of the ${buildAndPowerGcCount} marked caverns", + ) + .then( + pg( + ", upgrade them to Level 5, and keep them powered on.", + "and upgrade them all to Level 5. They must all be powered at the same time for the scans to work properly.", + ).then( + end, + pg("Finally,").then(collect_resources).then(cut), + pg("Then,"), + ), + ), + ) + .then( pg("find", "locate", "search the cavern for") .then( state("lostMinersOne") diff --git a/src/core/lore/graphs/premise.ts b/src/core/lore/graphs/premise.ts index 0ea777a..6741b25 100644 --- a/src/core/lore/graphs/premise.ts +++ b/src/core/lore/graphs/premise.ts @@ -361,5 +361,35 @@ const PREMISE = phraseGraph(({ pg, state, start, end, cut, skip }) => { ) .then(additionalHardship, end), ); + + // A joke from early in development of Hognose, here as an easter egg. + start + .then(state("hasGiantCave")) + .then( + "We've got news for you, Rock Raider! Our geological scanners have " + + 'discovered a nearby cave approximately the size of "yes".', + ) + .then(skip, state("hasMonsters")) + .then(skip, state("hasSlugs")) + .then(skip, state("spawnHasErosion")) + .then(skip, state("treasureCaveOne", "treasureCaveMany")) + .then(end); + + // Need to build Geological Centers in specific places. Blame "interference" + greeting + .then(state("buildAndPowerGcOne", "buildAndPowerGcMultiple")) + .then(skip, "We're sending you to a cavern deep within the planet.") + .then("Our long-range scanners", "The scanners up on the L.M.S. Explorer") + .then( + "are unable to penetrate the geology in this area", + "have been unreliable at this depth", + ) + .then( + "and we need some way to amplify them.", + "and we'd like to understand the area better.", + ) + .then(skip, "That's where you come in -") + .then("We need a team to scan the area.") + .then(end); }); export default PREMISE; diff --git a/src/core/lore/lore.ts b/src/core/lore/lore.ts index 874dc51..96f2376 100644 --- a/src/core/lore/lore.ts +++ b/src/core/lore/lore.ts @@ -2,6 +2,7 @@ import { HqMetadata } from "../architects/established_hq/base"; import { countLostMiners } from "../architects/lost_miners"; import { DiceBox } from "../common"; import { filterTruthy } from "../common/utils"; +import { GEOLOGICAL_CENTER } from "../models/building"; import { Plan } from "../models/plan"; import { FluidType, Tile } from "../models/tiles"; import { AdjuredCavern } from "../transformers/04_ephemera/01_adjure"; @@ -31,6 +32,9 @@ export type State = { readonly rockBiome: boolean; readonly iceBiome: boolean; readonly lavaBiome: boolean; + readonly hasGiantCave: boolean; + readonly buildAndPowerGcOne: boolean; + readonly buildAndPowerGcMultiple: boolean; }; export type FoundLostMinersState = State & { @@ -39,9 +43,10 @@ export type FoundLostMinersState = State & { }; type FormatVars = { + readonly buildAndPowerGcCount: string; + readonly enemies: string; readonly lostMinersCount: string; readonly lostMinerCavesCount: string; - readonly enemies: string; readonly resourceGoal: string; readonly resourceGoalNamesOnly: string; }; @@ -177,6 +182,15 @@ export class Lore { const findHq = !!hq && !spawnIsHq; const hqIsRuin = !!hq?.metadata.ruin; + const buildAndPowerGcCount = cavern.plans.reduce( + (r, p) => + p.metadata?.tag === "buildAndPower" && + p.metadata.template === GEOLOGICAL_CENTER + ? r + 1 + : r, + 0, + ); + const nomads = anchor.metadata?.tag === "nomads" ? (anchor.metadata.minersCount as number) @@ -187,6 +201,14 @@ export class Lore { 0, ); + const hasGiantCave = cavern.plans.some( + (plan) => + plan.pearlRadius > 20 && + // The actual diameter of the cave is almost definitely at least 60% the + // total size of the map. This can only happen with context overrides. + plan.pearlRadius * 3 > cavern.context.targetSize, + ); + this.state = { floodedWithWater: fluidType === Tile.WATER, floodedWithLava: fluidType === Tile.LAVA, @@ -211,6 +233,9 @@ export class Lore { rockBiome: cavern.context.biome === "rock", iceBiome: cavern.context.biome === "ice", lavaBiome: cavern.context.biome === "lava", + hasGiantCave, + buildAndPowerGcOne: buildAndPowerGcCount === 1, + buildAndPowerGcMultiple: buildAndPowerGcCount > 1, }; const enemies = filterTruthy([ @@ -227,6 +252,7 @@ export class Lore { enemies, lostMinersCount: spellNumber(lostMiners), lostMinerCavesCount: spellNumber(lostMinerCaves), + buildAndPowerGcCount: spellNumber(buildAndPowerGcCount), ...spellResourceGoal(cavern), }; } @@ -234,14 +260,26 @@ export class Lore { briefings(dice: DiceBox) { return { name: NAME.generate(dice.lore(LoreDie.name), this.state, this.formatVars), - premise: PREMISE.generate(dice.lore(LoreDie.premise), this.state, this.formatVars), - orders: ORDERS.generate(dice.lore(LoreDie.orders), this.state, this.formatVars), + premise: PREMISE.generate( + dice.lore(LoreDie.premise), + this.state, + this.formatVars, + ), + orders: ORDERS.generate( + dice.lore(LoreDie.orders), + this.state, + this.formatVars, + ), success: SUCCESS.generate( dice.lore(LoreDie.success), { ...this.state, commend: true }, this.formatVars, ), - failure: FAILURE.generate(dice.lore(LoreDie.failure), this.state, this.formatVars), + failure: FAILURE.generate( + dice.lore(LoreDie.failure), + this.state, + this.formatVars, + ), }; } } diff --git a/src/webui/components/map_preview/script_preview/index.tsx b/src/webui/components/map_preview/script_preview/index.tsx index 971b750..eae3763 100644 --- a/src/webui/components/map_preview/script_preview/index.tsx +++ b/src/webui/components/map_preview/script_preview/index.tsx @@ -95,9 +95,7 @@ export default function ScriptPreview({ onScroll={() => setScriptLineOffsets(getLineOffsets(ref.current!))} >

Script

-

- {statements?.length ?? 0} lines -

+

{statements?.length ?? 0} lines

{statements?.map(({ code, pos, kind }, i) => { const className = filterTruthy([ diff --git a/src/webui/components/map_preview/script_preview/styles.module.scss b/src/webui/components/map_preview/script_preview/styles.module.scss index f2696eb..659a44a 100644 --- a/src/webui/components/map_preview/script_preview/styles.module.scss +++ b/src/webui/components/map_preview/script_preview/styles.module.scss @@ -7,7 +7,8 @@ overflow-y: scroll; z-index: 1; - > h2, > p { + > h2, + > p { padding: 0 12px; }