From 194b67fde5965b22c36bfa3cbd771a62e3051c04 Mon Sep 17 00:00:00 2001 From: charredUtensil Date: Sun, 10 Nov 2024 12:48:15 -0500 Subject: [PATCH] prettier --- src/core/architects/blackout.ts | 42 ++-- src/core/architects/default.ts | 2 +- src/core/architects/established_hq/lost.ts | 4 +- src/core/architects/index.ts | 2 +- src/core/architects/lost_miners.ts | 41 ++-- src/core/architects/mob_farm.ts | 191 +++++++++++------- src/core/architects/seismic/boss_battle.ts | 2 +- src/core/architects/seismic/eruption.ts | 2 +- src/core/architects/slugs.ts | 2 +- src/core/architects/treasure.ts | 3 +- .../architects/utils/creature_spawners.ts | 4 +- src/core/architects/utils/script.test.ts | 6 +- src/core/architects/utils/script.ts | 12 +- src/core/common/geometry.test.ts | 19 +- src/core/common/geometry.ts | 7 +- src/core/lore/graphs/completeness.test.ts | 6 +- src/core/lore/graphs/events.ts | 23 ++- src/core/lore/graphs/premise.ts | 128 +++++++----- src/core/lore/lore.ts | 4 +- src/core/models/architect.ts | 5 +- src/core/transformers/02_masonry/02_brace.ts | 62 +++--- 21 files changed, 330 insertions(+), 237 deletions(-) diff --git a/src/core/architects/blackout.ts b/src/core/architects/blackout.ts index 68a37f0..3d5f61a 100644 --- a/src/core/architects/blackout.ts +++ b/src/core/architects/blackout.ts @@ -12,7 +12,7 @@ const MIN_AIR = 500; const RESET_SECONDS = 120; const RESET_END = 999; -const METADATA = {tag:'blackout'} as const satisfies BaseMetadata; +const METADATA = { tag: "blackout" } as const satisfies BaseMetadata; const BASE: PartialArchitect = { ...DefaultSpawnArchitect, @@ -21,22 +21,22 @@ const BASE: PartialArchitect = { const context = inferContextDefaults({ caveHasRechargeSeamChance: 0.15, hallHasRechargeSeamChance: 0.15, - ...cavern.initialContext - }) - return {...cavern, context} + ...cavern.initialContext, + }); + return { ...cavern, context }; }, - script({cavern, plan, sh}) { + script({ cavern, plan, sh }) { const v = mkVars(`p${plan.id}Bo`, [ - 'crystalBank', - 'doReset', - 'loop', - 'ms', - 'msgStart', - 'msgEnd', - 'needCrystals', - 'reset', - 'tc', - 'trips', + "crystalBank", + "doReset", + "loop", + "ms", + "msgStart", + "msgEnd", + "needCrystals", + "reset", + "tc", + "trips", ]); const rng = cavern.dice.script(plan.id); return scriptFragment( @@ -58,12 +58,12 @@ const BASE: PartialArchitect = { `when(crystals>=${v.needCrystals})[${v.trips}+=1]`, sh.trigger( `when(${v.trips}==1)`, - 'wait:random(5)(30);', + "wait:random(5)(30);", `${v.needCrystals}=crystals+${CRYSTALS_INCREMENT_TO_RESET};`, - 'disable:lights;', + "disable:lights;", `${v.reset}=0;`, `${v.loop};`, - 'wait:1;', + "wait:1;", `${v.ms}=1;`, ), `if(${v.ms}==1)[msg:${v.msgStart}]`, @@ -85,15 +85,15 @@ const BASE: PartialArchitect = { `crystals+=${v.crystalBank};`, `${v.needCrystals}=${v.crystalBank}+${CRYSTALS_INCREMENT_TO_RETRIGGER};`, `${v.crystalBank}=0;`, - 'wait:1;', - 'enable:lights;', + "wait:1;", + "enable:lights;", `${v.trips}=0;`, `wait:1;`, `${v.ms}=2;`, ), `if(${v.ms}==2)[msg:${v.msgEnd}]`, ); - } + }, }; const BLACKOUT = [ diff --git a/src/core/architects/default.ts b/src/core/architects/default.ts index 9655bd9..c4f6b04 100644 --- a/src/core/architects/default.ts +++ b/src/core/architects/default.ts @@ -91,4 +91,4 @@ export const DefaultSpawnArchitect: PartialArchitect = { }; }, maxSlope: 15, -}; \ No newline at end of file +}; diff --git a/src/core/architects/established_hq/lost.ts b/src/core/architects/established_hq/lost.ts index d824583..c7bfa2c 100644 --- a/src/core/architects/established_hq/lost.ts +++ b/src/core/architects/established_hq/lost.ts @@ -79,7 +79,7 @@ const LOST = [ !plan.fluid && plan.pearlRadius > 5 && hops.length <= MAX_HOPS && - plans[cavern.anchor]?.metadata?.tag !== 'mobFarm' && + plans[cavern.anchor]?.metadata?.tag !== "mobFarm" && !hops.some((id) => plans[id].fluid) && !plans.some((p) => p.metadata?.tag === "hq") && 0.5, @@ -95,7 +95,7 @@ const LOST = [ !plan.fluid && plan.pearlRadius > 6 && hops.length <= MAX_HOPS && - plans[cavern.anchor]?.metadata?.tag !== 'mobFarm' && + plans[cavern.anchor]?.metadata?.tag !== "mobFarm" && !plans.some((p) => p.metadata?.tag === "hq") && (plans[hops[0]].metadata?.tag === "nomads" ? 5 : 0.5), }, diff --git a/src/core/architects/index.ts b/src/core/architects/index.ts index b27d27a..93775d8 100644 --- a/src/core/architects/index.ts +++ b/src/core/architects/index.ts @@ -23,7 +23,7 @@ export type AnyMetadata = | LostMinersMetadata | NomadsMetadata | MobFarmMetadata - | { tag: 'blackout' | "seismic" | "slugNest" | "treasure" }; + | { tag: "blackout" | "seismic" | "slugNest" | "treasure" }; export const ARCHITECTS = [ ...BLACKOUT, diff --git a/src/core/architects/lost_miners.ts b/src/core/architects/lost_miners.ts index ffe56bd..e48e3df 100644 --- a/src/core/architects/lost_miners.ts +++ b/src/core/architects/lost_miners.ts @@ -78,14 +78,17 @@ function getBreadcrumbPoint( // Choose the neighboring plan which is closest to spawn (fewest hops). const neighborPlan = cavern.plans[plan.hops[plan.hops.length - 1]]; - const result = closestTo(minersPos, neighborPlan.innerPearl - .flatMap((layer) => layer) - // Find all the points in the inner pearl that are not walls and are in a - // different discovery zone from the miners. - .filter(([x, y]) => { - const dz = cavern.discoveryZones.get(x, y); - return dz && dz !== minersDz; - })); + const result = closestTo( + minersPos, + neighborPlan.innerPearl + .flatMap((layer) => layer) + // Find all the points in the inner pearl that are not walls and are in a + // different discovery zone from the miners. + .filter(([x, y]) => { + const dz = cavern.discoveryZones.get(x, y); + return dz && dz !== minersDz; + }), + ); // If such a point exists, return it. if (result) { @@ -105,16 +108,18 @@ function placeBreadcrumbVehicles( ): Vehicle[] { const tile = cavern.tiles.get(x, y); const fluid = tile === Tile.LAVA || tile === Tile.WATER ? tile : null; - const isMobFarm = cavern.plans[cavern.anchor].metadata?.tag === 'mobFarm'; - const template = rng.weightedChoice(filterTruthy([ - !fluid && !isMobFarm && { item: HOVER_SCOUT, bid: 2 }, - !fluid && { item: SMALL_DIGGER, bid: 0.5 }, - !fluid && { item: SMALL_TRANSPORT_TRUCK, bid: 0.75 }, - !fluid && { item: SMLC, bid: 0.05}, - !isMobFarm && fluid === Tile.WATER && { item: RAPID_RIDER, bid: 1 }, - !isMobFarm && { item: TUNNEL_SCOUT, bid: 0.25 }, - { item: null, bid: 0.0025 }, - ])); + const isMobFarm = cavern.plans[cavern.anchor].metadata?.tag === "mobFarm"; + const template = rng.weightedChoice( + filterTruthy([ + !fluid && !isMobFarm && { item: HOVER_SCOUT, bid: 2 }, + !fluid && { item: SMALL_DIGGER, bid: 0.5 }, + !fluid && { item: SMALL_TRANSPORT_TRUCK, bid: 0.75 }, + !fluid && { item: SMLC, bid: 0.05 }, + !isMobFarm && fluid === Tile.WATER && { item: RAPID_RIDER, bid: 1 }, + !isMobFarm && { item: TUNNEL_SCOUT, bid: 0.25 }, + { item: null, bid: 0.0025 }, + ]), + ); if (template) { return [ vehicleFactory.create({ diff --git a/src/core/architects/mob_farm.ts b/src/core/architects/mob_farm.ts index 6ed52a6..2bd4c00 100644 --- a/src/core/architects/mob_farm.ts +++ b/src/core/architects/mob_farm.ts @@ -4,14 +4,33 @@ import { DefaultSpawnArchitect, PartialArchitect } from "./default"; import { mkRough, Rough, weightedSprinkle } from "./utils/rough"; import { monsterSpawnScript } from "./utils/creature_spawners"; import { getBuildings } from "./utils/buildings"; -import { DOCKS, MINING_LASER, SUPER_TELEPORT, TOOL_STORE } from "../models/building"; +import { + DOCKS, + MINING_LASER, + SUPER_TELEPORT, + TOOL_STORE, +} from "../models/building"; import { position, randomlyInTile } from "../models/position"; import { asXY, closestTo, NSEW, offsetBy, Point } from "../common/geometry"; -import { CARGO_CARRIER, CHROME_CRUSHER, GRANITE_GRINDER, HOVER_SCOUT, LMLC, LOADER_DOZER, RAPID_RIDER, SMLC, TUNNEL_SCOUT, TUNNEL_TRANSPORT } from "../models/vehicle"; +import { + CARGO_CARRIER, + CHROME_CRUSHER, + GRANITE_GRINDER, + HOVER_SCOUT, + LMLC, + LOADER_DOZER, + RAPID_RIDER, + SMLC, + TUNNEL_SCOUT, + TUNNEL_TRANSPORT, +} from "../models/vehicle"; import { getPlaceRechargeSeams, sprinkleCrystals } from "./utils/resources"; import { inferContextDefaults } from "../common"; import { mkVars, scriptFragment } from "./utils/script"; -import { HINT_SELECT_LASER_GROUP, MOB_FARM_NO_LONGER_BLOCKING } from "../lore/graphs/events"; +import { + HINT_SELECT_LASER_GROUP, + MOB_FARM_NO_LONGER_BLOCKING, +} from "../lore/graphs/events"; import { gObjectives } from "./utils/objectives"; import { PreprogrammedCavern } from "../transformers/04_ephemera/03_preprogram"; @@ -30,11 +49,11 @@ const BANLIST = [ ] as const; export type MobFarmMetadata = { - tag: "mobFarm", - hoardSize: number, + tag: "mobFarm"; + hoardSize: number; }; -function totalAccessibleWalls({aerationLog, tiles}: PreprogrammedCavern) { +function totalAccessibleWalls({ aerationLog, tiles }: PreprogrammedCavern) { let result = 0; aerationLog?.forEach((_, x, y) => tiles.get(x, y)?.isWall && result++); return result; @@ -42,35 +61,47 @@ function totalAccessibleWalls({aerationLog, tiles}: PreprogrammedCavern) { const BASE: PartialArchitect = { ...DefaultSpawnArchitect, - prime: ({cavern, plan}) => ({tag: "mobFarm", hoardSize: cavern.dice.prime(plan.id).betaInt({a: 4, b: 4, min: 170, max: 230})}), + prime: ({ cavern, plan }) => ({ + tag: "mobFarm", + hoardSize: cavern.dice + .prime(plan.id) + .betaInt({ a: 4, b: 4, min: 170, max: 230 }), + }), mod(cavern) { const context = inferContextDefaults({ caveCrystalRichness: { base: -0.16, hops: 0.32, order: 0.32 }, hallCrystalRichness: { base: 0, hops: 0, order: 0 }, caveCrystalSeamBias: 0.7, globalHostilesCap: 10, - ...cavern.initialContext - }) - return {...cavern, context} + ...cavern.initialContext, + }); + return { ...cavern, context }; }, - crystalsToPlace: ({ plan }) => Math.max(plan.crystalRichness * plan.perimeter, 9), + crystalsToPlace: ({ plan }) => + Math.max(plan.crystalRichness * plan.perimeter, 9), crystalsFromMetadata: (metadata) => 4 + LMLC.crystals + metadata.hoardSize, placeRechargeSeam: getPlaceRechargeSeams(3), placeBuildings(args) { - const [toolStore] = getBuildings({ - from: 4, - queue: [(pos) => TOOL_STORE.atTile({ ...pos })], - }, args); + const [toolStore] = getBuildings( + { + from: 4, + queue: [(pos) => TOOL_STORE.atTile({ ...pos })], + }, + args, + ); toolStore.foundation.forEach(([x, y]) => args.tiles.set(x, y, Tile.FOUNDATION), ); args.openCaveFlags.set(...toolStore.foundation[0], true); const seamPos = closestTo( toolStore.foundation[0], - args.plan.innerPearl.flatMap(ly => ly.filter(pos => { - const t = args.tiles.get(...pos); - return t === Tile.DIRT || t === Tile.LOOSE_ROCK; - })))! + args.plan.innerPearl.flatMap((ly) => + ly.filter((pos) => { + const t = args.tiles.get(...pos); + return t === Tile.DIRT || t === Tile.LOOSE_ROCK; + }), + ), + )!; args.tiles.set(...seamPos, Tile.CRYSTAL_SEAM); return { buildings: [toolStore], @@ -78,40 +109,46 @@ const BASE: PartialArchitect = { ...asXY(args.plan.innerPearl[0][0]), aimedAt: [toolStore.x, toolStore.y], pitch: Math.PI / 3, - }) - } + }), + }; }, placeCrystals(args) { sprinkleCrystals(args); - const tiles = args.plan.innerPearl.flatMap((ly, i) => i <= 2 ? ly : []).filter(pos => { - const t = args.tiles.get(...pos); - return t && !t.isWall && !t.isFluid; - }) + const tiles = args.plan.innerPearl + .flatMap((ly, i) => (i <= 2 ? ly : [])) + .filter((pos) => { + const t = args.tiles.get(...pos); + return t && !t.isWall && !t.isFluid; + }); const rng = args.cavern.dice.placeCrystals(args.plan.id); sprinkleCrystals(args, { - getRandomTile: () => rng.betaChoice(tiles, {a: 1, b: 2.5}), + getRandomTile: () => rng.betaChoice(tiles, { a: 1, b: 2.5 }), seamBias: 0, count: args.plan.metadata.hoardSize, }); }, placeSlugHoles() {}, placeLandslides() {}, - placeEntities({cavern, plan, minerFactory, vehicleFactory}) { + placeEntities({ cavern, plan, minerFactory, vehicleFactory }) { const rng = cavern.dice.placeEntities(plan.id); - const ts = cavern.buildings.find(b => cavern.pearlInnerDex.get(...b.foundation[0])?.[plan.id])!; - const tiles = NSEW.map(oPos => offsetBy(ts.foundation[0], oPos)).filter(pos => { - const t = cavern.tiles.get(...pos); - return t && !t.isWall && !t.isFluid; - }) - const pos = randomlyInTile({...asXY(rng.uniformChoice(tiles)), rng}); + const ts = cavern.buildings.find( + (b) => cavern.pearlInnerDex.get(...b.foundation[0])?.[plan.id], + )!; + const tiles = NSEW.map((oPos) => offsetBy(ts.foundation[0], oPos)).filter( + (pos) => { + const t = cavern.tiles.get(...pos); + return t && !t.isWall && !t.isFluid; + }, + ); + const pos = randomlyInTile({ ...asXY(rng.uniformChoice(tiles)), rng }); const driver = minerFactory.create({ - loadout: ['Drill', 'JobDriver', 'JobEngineer'], + loadout: ["Drill", "JobDriver", "JobEngineer"], planId: plan.id, ...pos, - }) + }); const lmlc = vehicleFactory.create({ template: LMLC, - upgrades: ['UpLaser'], + upgrades: ["UpLaser"], planId: plan.id, driverId: driver.id, ...pos, @@ -119,34 +156,43 @@ const BASE: PartialArchitect = { return { miners: [driver], vehicles: [lmlc], - } + }; }, - objectives({cavern}) { + objectives({ cavern }) { const crystals = cavern.plans[cavern.anchor].crystals * 0.6; return { - crystals: Math.floor(crystals / 5) * 5, sufficient: true + crystals: Math.floor(crystals / 5) * 5, + sufficient: true, }; }, - script({cavern, plan, sh}) { - const v = mkVars(`p${plan.id}MF`, ['hintGroup', 'msgHintGroup', 'msgNotBlocking']) - const rng = cavern.dice.script(plan.id) + script({ cavern, plan, sh }) { + const v = mkVars(`p${plan.id}MF`, [ + "hintGroup", + "msgHintGroup", + "msgNotBlocking", + ]); + const rng = cavern.dice.script(plan.id); return scriptFragment( - '# Globals: Mob Farm', + "# Globals: Mob Farm", sh.trigger( - 'if(time:0)', - ...BANLIST.map(t => `disable:${t.id};` satisfies `${string};`) + "if(time:0)", + ...BANLIST.map((t) => `disable:${t.id};` satisfies `${string};`), ), - cavern.objectives.variables.length > 0 && scriptFragment( - sh.declareString(v.msgNotBlocking, {rng, pg: MOB_FARM_NO_LONGER_BLOCKING}), - sh.trigger( - // There's a good chance any further objectives are softlocked by the - // inability to cross lakes and rivers - so unlock them. - `if(crystals>=${cavern.objectives.crystals})`, - `wait:5;`, - ...BANLIST.map(t => `enable:${t.id};` satisfies `${string};`), - `((${gObjectives.won}==0))msg:${v.msgNotBlocking};`, + cavern.objectives.variables.length > 0 && + scriptFragment( + sh.declareString(v.msgNotBlocking, { + rng, + pg: MOB_FARM_NO_LONGER_BLOCKING, + }), + sh.trigger( + // There's a good chance any further objectives are softlocked by the + // inability to cross lakes and rivers - so unlock them. + `if(crystals>=${cavern.objectives.crystals})`, + `wait:5;`, + ...BANLIST.map((t) => `enable:${t.id};` satisfies `${string};`), + `((${gObjectives.won}==0))msg:${v.msgNotBlocking};`, + ), ), - ), // Hint to tell players about control groups. This isn't super annoying // under normal circumstances, but here it's almost a necessity that the // player have their lasers bound to a single key. @@ -162,17 +208,15 @@ const BASE: PartialArchitect = { `${v.hintGroup}=1;`, ), sh.declareString(v.msgHintGroup, HINT_SELECT_LASER_GROUP), - sh.trigger( - `if(${v.hintGroup}>0)`, - `msg:${v.msgHintGroup};`, - ), + sh.trigger(`if(${v.hintGroup}>0)`, `msg:${v.msgHintGroup};`), ); }, - monsterSpawnScript: (args) => monsterSpawnScript(args, { - initialCooldown: {min: 120, max: 240}, - needCrystals: { base: 2 }, - tripOnArmed: 'always', - }) + monsterSpawnScript: (args) => + monsterSpawnScript(args, { + initialCooldown: { min: 120, max: 240 }, + needCrystals: { base: 2 }, + tripOnArmed: "always", + }), }; const MOB_FARM = [ @@ -183,20 +227,22 @@ const MOB_FARM = [ { of: Rough.ALWAYS_FLOOR, width: 2 }, { of: Rough.WATER, width: 2, grow: 0.5 }, { of: Rough.FLOOR, grow: 1 }, - { of: weightedSprinkle( - {item: Rough.LOOSE_ROCK, bid: 5}, - {item: Rough.FLOOR, bid: 1 - })}, + { + of: weightedSprinkle( + { item: Rough.LOOSE_ROCK, bid: 5 }, + { item: Rough.FLOOR, bid: 1 }, + ), + }, { of: weightedSprinkle( { item: Rough.LOOSE_ROCK, bid: 10 }, { item: Rough.LOOSE_OR_HARD_ROCK, bid: 1 }, ), }, - {of: Rough.MIX_FRINGE}, + { of: Rough.MIX_FRINGE }, ), anchorBid: ({ cavern, plan }) => - cavern.context.biome === 'ice' && + cavern.context.biome === "ice" && cavern.context.hasMonsters && plan.fluid === Tile.WATER && plan.pearlRadius > 6 && @@ -205,7 +251,10 @@ const MOB_FARM = [ const p = cavern.plans[i]; return !p.fluid && p.lakeSize >= 6; }) && - plan.intersects.reduce((r, _, i) => cavern.plans[i].fluid ? r + 1 : r, 0) <= 1 && + plan.intersects.reduce( + (r, _, i) => (cavern.plans[i].fluid ? r + 1 : r), + 0, + ) <= 1 && 2, }, ] as const satisfies readonly Architect[]; diff --git a/src/core/architects/seismic/boss_battle.ts b/src/core/architects/seismic/boss_battle.ts index 481f27a..7e66ff1 100644 --- a/src/core/architects/seismic/boss_battle.ts +++ b/src/core/architects/seismic/boss_battle.ts @@ -172,7 +172,7 @@ const BASE: PartialArchitect = { monsterSpawnScript: (args) => { return monsterSpawnScript(args, { armEvent: sVars(args.plan).doArm, - tripOnArmed: 'first', + tripOnArmed: "first", }); }, }; diff --git a/src/core/architects/seismic/eruption.ts b/src/core/architects/seismic/eruption.ts index 6b473eb..9fddca0 100644 --- a/src/core/architects/seismic/eruption.ts +++ b/src/core/architects/seismic/eruption.ts @@ -93,7 +93,7 @@ const BASE: PartialArchitect = { monsterSpawnScript: (args) => { return monsterSpawnScript(args, { armEvent: sVars(args.plan).doSpawn, - tripOnArmed: 'first', + tripOnArmed: "first", }); }, }; diff --git a/src/core/architects/slugs.ts b/src/core/architects/slugs.ts index 3ccf1c7..51a5c78 100644 --- a/src/core/architects/slugs.ts +++ b/src/core/architects/slugs.ts @@ -51,7 +51,7 @@ const SLUG_NEST: PartialArchitect = { reArmMode: "none", initialCooldown: { min: 20, max: 60 }, needCrystals: { base: Math.floor(getTotalCrystals(args.cavern) / 10) }, - tripOnArmed: 'first', + tripOnArmed: "first", waveSize: holeCount, }); }, diff --git a/src/core/architects/treasure.ts b/src/core/architects/treasure.ts index 89c35ee..f2d4993 100644 --- a/src/core/architects/treasure.ts +++ b/src/core/architects/treasure.ts @@ -39,7 +39,8 @@ const BASE: PartialArchitect = { const g = mkVars("gHoard", ["lock", "message", "crystalsAvailable"]); const shouldIncludeHoardScript = (cavern: PreprogrammedCavern) => - cavern.objectives.crystals && cavern.plans[cavern.anchor].metadata?.tag !== 'mobFarm'; + cavern.objectives.crystals && + cavern.plans[cavern.anchor].metadata?.tag !== "mobFarm"; const HOARD: typeof BASE = { ...BASE, diff --git a/src/core/architects/utils/creature_spawners.ts b/src/core/architects/utils/creature_spawners.ts index f23bf40..a8fca0b 100644 --- a/src/core/architects/utils/creature_spawners.ts +++ b/src/core/architects/utils/creature_spawners.ts @@ -42,7 +42,7 @@ type CreatureSpawnerArgs = { readonly initialCooldown?: { min: number; max: number }; readonly needCrystals?: { base: number; increment?: number }; readonly needStableAir?: boolean; - readonly tripOnArmed?: 'first' | 'always'; + readonly tripOnArmed?: "first" | "always"; readonly tripPoints?: readonly Point[]; } ); @@ -287,7 +287,7 @@ function creatureSpawnScript( `wait:random(${cooldown.min.toFixed(2)})(${cooldown.max.toFixed(2)});`, opts.reArmMode === "hoard" && `((${v.hoardTrip}==0))return;`, `${v.arm}=1;`, - opts.tripOnArmed === 'always' && `${v.doTrip};`, + opts.tripOnArmed === "always" && `${v.doTrip};`, ); })(), ); diff --git a/src/core/architects/utils/script.test.ts b/src/core/architects/utils/script.test.ts index 8cb1aee..637ad3f 100644 --- a/src/core/architects/utils/script.test.ts +++ b/src/core/architects/utils/script.test.ts @@ -11,13 +11,15 @@ describe("escapeString", () => { }); it("removes quotes", () => { expect(sanitizeString('An "Energy Crystal" has been found!')).toBe( - 'An Energy Crystal has been found!', + "An Energy Crystal has been found!", ); }); it("removes backslashes", () => { expect(sanitizeString("\\n\\n\\n\\")).toBe("nnn"); }); it("removes newlines", () => { - expect(sanitizeString("one\ntwo\n three \n \n \n four")).toBe("one two three four"); + expect(sanitizeString("one\ntwo\n three \n \n \n four")).toBe( + "one two three four", + ); }); }); diff --git a/src/core/architects/utils/script.ts b/src/core/architects/utils/script.ts index 45d4bd0..ae64d09 100644 --- a/src/core/architects/utils/script.ts +++ b/src/core/architects/utils/script.ts @@ -36,9 +36,13 @@ export function scriptFragment(...rest: ScriptLine[]) { return rest.filter((s) => s).join("\n") as any; } -export function check(condition: string, ifTrue: string, ifFalse?: string | Falsy): EventChainLine { +export function check( + condition: string, + ifTrue: string, + ifFalse?: string | Falsy, +): EventChainLine { if (ifFalse) { - return `((${condition}))[${ifTrue}][${ifFalse}];` + return `((${condition}))[${ifTrue}][${ifFalse}];`; } return `((${condition}))${ifTrue};`; } @@ -48,7 +52,7 @@ export function eventChain(name: string, ...rest: EventChainLine[]) { } export function sanitizeString(s: string) { - return s.replace(/[\\"]+/g, "").replace(/\s*\n[\s\n]*/g, ' '); + return s.replace(/[\\"]+/g, "").replace(/\s*\n[\s\n]*/g, " "); } type DieOrRng = @@ -132,7 +136,7 @@ export class ScriptHelperImpl implements ScriptHelper { */ trigger(condition: Trigger, ...rest: EventChainLine[]) { const lines = filterTruthy(rest); - if (lines.length === 1 && !lines.some(line => line.includes('\n'))) { + if (lines.length === 1 && !lines.some((line) => line.includes("\n"))) { return `${condition}[${lines[0].substring(0, lines[0].length - 1)}]`; } const name = `ec${this._uid++}`; diff --git a/src/core/common/geometry.test.ts b/src/core/common/geometry.test.ts index 51112a4..f8ed615 100644 --- a/src/core/common/geometry.test.ts +++ b/src/core/common/geometry.test.ts @@ -1,19 +1,26 @@ import { closestTo, plotLine } from "./geometry"; - -describe('closestTo', () => { - it('should return null for an empty list of points', () => { +describe("closestTo", () => { + it("should return null for an empty list of points", () => { const result = closestTo([0, 0], []); expect(result).toBeNull(); }); - it('should return the only point in the list', () => { + it("should return the only point in the list", () => { const result = closestTo([0, 0], [[1, 1]]); expect(result).toEqual([1, 1]); }); - it('should return the closest point', () => { - const result = closestTo([7, -2], [[1, 1], [6, -1], [14, -2], [-7, 2]]); + it("should return the closest point", () => { + const result = closestTo( + [7, -2], + [ + [1, 1], + [6, -1], + [14, -2], + [-7, 2], + ], + ); expect(result).toEqual([6, -1]); }); }); diff --git a/src/core/common/geometry.ts b/src/core/common/geometry.ts index 727dbe6..eed8e5e 100644 --- a/src/core/common/geometry.ts +++ b/src/core/common/geometry.ts @@ -41,10 +41,13 @@ export function isAdjacent8(a: Point, b: Point) { } export function asXY([x, y]: Point) { - return {x, y}; + return { x, y }; } -export function closestTo([x, y]: Point, points: readonly Point[]): Point | null { +export function closestTo( + [x, y]: Point, + points: readonly Point[], +): Point | null { if (!points.length) { return null; } diff --git a/src/core/lore/graphs/completeness.test.ts b/src/core/lore/graphs/completeness.test.ts index 360b97c..35639bf 100644 --- a/src/core/lore/graphs/completeness.test.ts +++ b/src/core/lore/graphs/completeness.test.ts @@ -47,10 +47,10 @@ function expectCompletion(actual: PhraseGraph) { .then(skip, st("spawnHasErosion")) .then(skip, st("treasureCaveOne", "treasureCaveMany")) .then( - pg(skip, st("spawnIsNomadOne", "spawnIsNomadsTogether", "spawnIsBlackout")).then( + pg( skip, - st("findHq").then(skip, st("hqIsRuin")), - ), + st("spawnIsNomadOne", "spawnIsNomadsTogether", "spawnIsBlackout"), + ).then(skip, st("findHq").then(skip, st("hqIsRuin"))), st("spawnIsHq").then(skip, st("hqIsFixedComplete"), st("hqIsRuin")), st("spawnIsMobFarm"), ) diff --git a/src/core/lore/graphs/events.ts b/src/core/lore/graphs/events.ts index e6e66e4..b8fd908 100644 --- a/src/core/lore/graphs/events.ts +++ b/src/core/lore/graphs/events.ts @@ -289,10 +289,7 @@ export const BLACKOUT_START = phraseGraph( "Blackout Start", ({ pg, state, start, end, cut, skip }) => { start - .then( - skip, - "Oh no!", - ) + .then(skip, "Oh no!") .then("The magnetic shifts are interfering with our Power Station.") .then(end); }, @@ -303,7 +300,12 @@ export const BLACKOUT_END = phraseGraph( ({ pg, state, start, end, cut, skip }) => { start .then("The power is back!") - .then(skip, state("hasAirLimit").then("I suggest you build additional Support Stations.")) + .then( + skip, + state("hasAirLimit").then( + "I suggest you build additional Support Stations.", + ), + ) .then(end); }, ); @@ -314,20 +316,21 @@ export const MOB_FARM_NO_LONGER_BLOCKING = phraseGraph( start .then( "With so many Energy Crystals removed, you should now have no " + - "issues teleporting in the other vehicles.", + "issues teleporting in the other vehicles.", ) .then( skip, state("lostMinersOne").then( - "Use them to find that missing Rock Raider!" + "Use them to find that missing Rock Raider!", ), state("lostMinersTogether", "lostMinersApart").then( - "Use them to find those missing Rock Raiders!" - )) + "Use them to find those missing Rock Raiders!", + ), + ) .then(end); }, ); export const HINT_SELECT_LASER_GROUP = `Hint: Hold SHIFT+click to select multiple units. CTRL+[0-9] assigns a group of units that you can recall with [0-9]. -X activates laser mode.`; \ No newline at end of file +X activates laser mode.`; diff --git a/src/core/lore/graphs/premise.ts b/src/core/lore/graphs/premise.ts index 41e1f4d..7b6c35e 100644 --- a/src/core/lore/graphs/premise.ts +++ b/src/core/lore/graphs/premise.ts @@ -41,17 +41,17 @@ const PREMISE = phraseGraph( const spawnHasErosion = state("spawnHasErosion").then( "we are dangerously close to a cavern full of lava", "we are concerned about nearby lava flows that could engulf this " + - "cavern", + "cavern", "you will need to keep an eye on the volcanic activity in this " + - "cavern to avoid being buried in lava", + "cavern to avoid being buried in lava", ); const blackout = state("spawnIsBlackout").then( "the unusual magnetic properties of the rock here might interfere " + - "with our equipment", + "with our equipment", "there are unusual magnetic readings in this cavern and we're " + - "concerned about the effects that might have on our equipment" - ) + "concerned about the effects that might have on our equipment", + ); const hasMonstersTexts = pg( state("hasMonsters").then(skip, state("hasSlugs")), @@ -79,8 +79,15 @@ const PREMISE = phraseGraph( "you must make do with the buildings that are already constructed.", ); - pg(spawnHasErosion, blackout.then(skip, state("spawnHasErosion"))).then(", and").then(hasMonstersTexts); - return pg(blackout, spawnHasErosion, hasMonstersTexts, hqIsFixedComplete.then(end)) + pg(spawnHasErosion, blackout.then(skip, state("spawnHasErosion"))) + .then(", and") + .then(hasMonstersTexts); + return pg( + blackout, + spawnHasErosion, + hasMonstersTexts, + hqIsFixedComplete.then(end), + ) .then(".") .then(end, hqIsFixedComplete); })(); @@ -169,12 +176,14 @@ const PREMISE = phraseGraph( ), "another cavern where we can continue our mining operations", ), - state("spawnIsBlackout").then( - "We found a cavern with unusual geomagnetic properties. We believe " + - "it will have plenty of Energy Crystals", - "We're sending you to a cavern deep within the planet where we've " + - "been picking up unusual magnetic readings", - ).then(skip, state("treasureCaveOne", "treasureCaveMany")), + state("spawnIsBlackout") + .then( + "We found a cavern with unusual geomagnetic properties. We believe " + + "it will have plenty of Energy Crystals", + "We're sending you to a cavern deep within the planet where we've " + + "been picking up unusual magnetic readings", + ) + .then(skip, state("treasureCaveOne", "treasureCaveMany")), ) .then( pg(".").then(end), @@ -232,50 +241,57 @@ const PREMISE = phraseGraph( // TODO: need lore state and copy for blackout? - greeting.then(state('spawnIsMobFarm')).then( - "We discovered this incredible cave with the abundance of Energy " + - "Crystals you now see before you.", - "As you can see, we have located a cave with an absurd number of " + - "Energy Crystals.", - ).then( - "We meant to teleport you onto that island, but something is " + - "interfering with the signal.", - "That many Energy Crystals in one place seems to be interfering with " + - "our teleporters.", - ).then( - "We are extremely limited in what vehicles we can send down to you, " + - "so you'll have to get the crystals some other way.", - ).then( - skip, state('hasMonsters') - ).then( - skip, state('hasSlugs') - ).then( - skip, state('spawnHasErosion') - ).then( - skip, state('treasureCaveOne', 'treasureCaveMany') - ).then( - skip, - pg("\n\nThere's one more thing - ").then(skip, "you aren't the first to arrive here.").then( - state('lostMinersApart').then( - "Some of our Rock Raiders were scattered a bit further away from " + - "the island. By our readings, they seem to be in separate caverns " + - "nearby" - ), - state('lostMinersTogether').then( - "We already sent a team down here, but they failed to check in").then(skip, ". We believe they are stranded in a nearby cavern", - ), - state('lostMinersOne').then( - "One of our Rock Raiders was teleported to another cavern " + - "somewhere near here", - "One of our Rock Raiders didn't come down with the group. They " + - "should be somewhere nearby" - ), - ).then( - "and we're counting on you to rescue them!", - ". I know I can count on you to reach them.", - ".", + greeting + .then(state("spawnIsMobFarm")) + .then( + "We discovered this incredible cave with the abundance of Energy " + + "Crystals you now see before you.", + "As you can see, we have located a cave with an absurd number of " + + "Energy Crystals.", + ) + .then( + "We meant to teleport you onto that island, but something is " + + "interfering with the signal.", + "That many Energy Crystals in one place seems to be interfering with " + + "our teleporters.", + ) + .then( + "We are extremely limited in what vehicles we can send down to you, " + + "so you'll have to get the crystals some other way.", ) - ).then(end); + .then(skip, state("hasMonsters")) + .then(skip, state("hasSlugs")) + .then(skip, state("spawnHasErosion")) + .then(skip, state("treasureCaveOne", "treasureCaveMany")) + .then( + skip, + pg("\n\nThere's one more thing - ") + .then(skip, "you aren't the first to arrive here.") + .then( + state("lostMinersApart").then( + "Some of our Rock Raiders were scattered a bit further away from " + + "the island. By our readings, they seem to be in separate caverns " + + "nearby", + ), + state("lostMinersTogether") + .then( + "We already sent a team down here, but they failed to check in", + ) + .then(skip, ". We believe they are stranded in a nearby cavern"), + state("lostMinersOne").then( + "One of our Rock Raiders was teleported to another cavern " + + "somewhere near here", + "One of our Rock Raiders didn't come down with the group. They " + + "should be somewhere nearby", + ), + ) + .then( + "and we're counting on you to rescue them!", + ". I know I can count on you to reach them.", + ".", + ), + ) + .then(end); const negativeGreeting = pg( greeting, diff --git a/src/core/lore/lore.ts b/src/core/lore/lore.ts index fed33d1..9ff88ce 100644 --- a/src/core/lore/lore.ts +++ b/src/core/lore/lore.ts @@ -241,8 +241,8 @@ export class Lore { buildAndPowerGcOne: buildAndPowerGcCount === 1, buildAndPowerGcMultiple: buildAndPowerGcCount > 1, hasAirLimit: !!cavern.oxygen, - spawnIsMobFarm: anchor.metadata?.tag === 'mobFarm', - spawnIsBlackout: anchor.metadata?.tag === 'blackout', + spawnIsMobFarm: anchor.metadata?.tag === "mobFarm", + spawnIsBlackout: anchor.metadata?.tag === "blackout", }; const enemies = filterTruthy([ diff --git a/src/core/models/architect.ts b/src/core/models/architect.ts index 3454ee6..23ecd48 100644 --- a/src/core/models/architect.ts +++ b/src/core/models/architect.ts @@ -30,10 +30,7 @@ import { AnchoredCavern, OrderedPlan, } from "../transformers/01_planning/03_anchor"; -import { - DzPriority, - ScriptHelper, -} from "../architects/utils/script"; +import { DzPriority, ScriptHelper } from "../architects/utils/script"; export type BaseMetadata = { readonly tag: string } | undefined; diff --git a/src/core/transformers/02_masonry/02_brace.ts b/src/core/transformers/02_masonry/02_brace.ts index 6bd3335..2327400 100644 --- a/src/core/transformers/02_masonry/02_brace.ts +++ b/src/core/transformers/02_masonry/02_brace.ts @@ -1,11 +1,17 @@ import { MutableGrid } from "../../common/grid"; import { RoughPlasticCavern } from "./01_rough"; import { Tile } from "../../models/tiles"; -import { NSEW, offsetBy, Point, rotateAround, rotateLeft, rotateRight } from "../../common/geometry"; +import { + NSEW, + offsetBy, + Point, + rotateAround, + rotateLeft, + rotateRight, +} from "../../common/geometry"; import { DiscoveryZone, getDiscoveryZones } from "../../models/discovery_zone"; import { filterTruthy } from "../../common/utils"; - /* Each tile is part of four different possible 2x2 squares. If the tile is floor, it does not need to be braced. @@ -14,7 +20,6 @@ import { filterTruthy } from "../../common/utils"; If it needs to be braced, */ - export default function brace(cavern: RoughPlasticCavern): RoughPlasticCavern { const rng = cavern.dice.brace; const tiles = cavern.tiles.copy(); @@ -34,38 +39,37 @@ export default function brace(cavern: RoughPlasticCavern): RoughPlasticCavern { // . W E // A V D // Z S . - const squares = rng.shuffle(NSEW).map( - ([owx, owy]) => { - const pw = offsetBy(pv, [owx, owy]); - const pa = offsetBy(pv, [owy, -owx]); - const ps = offsetBy(pv, [-owx, -owy]); - const pd = offsetBy(pv, [-owy, owx]); - const pe = offsetBy(pv, [owx - owy, owy + owx]) - const pz = offsetBy(pv, [-owx + owy, -owy - owx]) - const floors = [pw, pe, pd].reduce((r, p) => tiles.get(...p)?.isWall === false ? r + 1 : r, 0); - return {pw, pa, ps, pd, pe, pz, floors}; - } - ); + const squares = rng.shuffle(NSEW).map(([owx, owy]) => { + const pw = offsetBy(pv, [owx, owy]); + const pa = offsetBy(pv, [owy, -owx]); + const ps = offsetBy(pv, [-owx, -owy]); + const pd = offsetBy(pv, [-owy, owx]); + const pe = offsetBy(pv, [owx - owy, owy + owx]); + const pz = offsetBy(pv, [-owx + owy, -owy - owx]); + const floors = [pw, pe, pd].reduce( + (r, p) => (tiles.get(...p)?.isWall === false ? r + 1 : r), + 0, + ); + return { pw, pa, ps, pd, pe, pz, floors }; + }); // Sort the squares by how many floor tiles they have so the most supported // goes first. squares.sort((a, b) => a.floors - b.floors); - for (const {pw, pa, ps, pd, pe, pz, floors} of squares) { + for (const { pw, pa, ps, pd, pe, pz, floors } of squares) { // All points are already walls - nothing to do. if (floors === 0) { - [pv, pw, pe, pd].forEach(p => done.set(...p, true)); + [pv, pw, pe, pd].forEach((p) => done.set(...p, true)); return; } // Determine if this separates two discovery zones. If so, it doesn't // need to be supported. const dzs: DiscoveryZone[] = []; - [pw, pe, pd, ps, pz, pa].forEach( - p => { - if (tiles.get(...p)?.isWall === false) { - const dz = discoveryZones.get(...p)!; - dzs[dz.id] = dz; - } + [pw, pe, pd, ps, pz, pa].forEach((p) => { + if (tiles.get(...p)?.isWall === false) { + const dz = discoveryZones.get(...p)!; + dzs[dz.id] = dz; } - ); + }); if (dzs.reduce((r) => r + 1, 0) > 1) { const [d1, d2] = dzs.filter(() => true); if (!d1.openOnSpawn || !d2.openOnSpawn) { @@ -78,7 +82,7 @@ export default function brace(cavern: RoughPlasticCavern): RoughPlasticCavern { } } // This square must become wall. - [pw, pe, pd].forEach(p => { + [pw, pe, pd].forEach((p) => { if (tiles.get(...p)?.isWall === false) { tiles.set(...p, Tile.DIRT); } @@ -89,9 +93,11 @@ export default function brace(cavern: RoughPlasticCavern): RoughPlasticCavern { } } - const queue: Point[] = rng.shuffle(tiles.flatMap( - (_, x, y) => [[0,0], ...NSEW].map(([ox, oy]) => [x + ox, y + oy] satisfies Point) - )); + const queue: Point[] = rng.shuffle( + tiles.flatMap((_, x, y) => + [[0, 0], ...NSEW].map(([ox, oy]) => [x + ox, y + oy] satisfies Point), + ), + ); queue.forEach(visit); return { ...cavern, tiles }; }