Skip to content

Commit

Permalink
0.10 - Rework cavern generation (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
charredUtensil authored Aug 8, 2024
1 parent 1af62b6 commit 5f296dc
Show file tree
Hide file tree
Showing 39 changed files with 1,008 additions and 545 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "groundhog",
"version": "0.9.6",
"version": "0.10.0",
"homepage": "https://charredutensil.github.io/groundhog",
"private": true,
"dependencies": {
Expand Down
Empty file added src/core/architects/collapse.ts
Empty file.
20 changes: 11 additions & 9 deletions src/core/architects/established_hq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { Tile } from "../models/tiles";
import { DefaultCaveArchitect, PartialArchitect } from "./default";
import { MakeBuildingFn, getBuildings } from "./utils/buildings";
import { Rough, RoughOyster } from "./utils/oyster";
import { mkRough, Rough } from "./utils/rough";
import { position } from "../models/position";
import { getPlaceRechargeSeams, sprinkleCrystals } from "./utils/resources";
import { placeLandslides } from "./utils/hazards";
Expand Down Expand Up @@ -224,7 +224,7 @@ function getPlaceBuildings({
};
}

export const gFoundHq = mkVars("gFoundHq", ["foundHq"]);
export const gLostHq = mkVars("gLostHq", ["foundHq"]);

const WITH_FIND_OBJECTIVE: Pick<
Architect<HqMetadata>,
Expand All @@ -233,14 +233,14 @@ const WITH_FIND_OBJECTIVE: Pick<
objectives: () => ({
variables: [
{
condition: `${gFoundHq.foundHq}>0`,
condition: `${gLostHq.foundHq}>0`,
description: "Find the lost Rock Raider HQ",
},
],
sufficient: false,
}),
scriptGlobals: () =>
scriptFragment("# Lost HQ Globals", `int ${gFoundHq.foundHq}=0`),
scriptFragment("# Globals: Lost HQ", `int ${gLostHq.foundHq}=0`),
script({ cavern, plan }) {
const discoPoint = getDiscoveryPoint(cavern, plan);
if (!discoPoint) {
Expand All @@ -251,19 +251,19 @@ const WITH_FIND_OBJECTIVE: Pick<
return r.pearlRadius > p.pearlRadius ? r : p;
}).center;

const v = mkVars(`p${plan.id}FoundHq`, ["messageDiscover", "onDiscover"]);
const v = mkVars(`p${plan.id}LostHq`, ["messageDiscover", "onDiscover"]);
const message = cavern.lore.foundHq(cavern.dice).text;

return scriptFragment(
`# Lost HQ ${plan.id}`,
`# P${plan.id}: Lost HQ`,
`string ${v.messageDiscover}="${escapeString(message)}"`,
`if(change:${transformPoint(cavern, discoPoint)})[${v.onDiscover}]`,
eventChain(
v.onDiscover,
`msg:${v.messageDiscover};`,
`pan:${transformPoint(cavern, camPoint)};`,
`wait:1;`,
`${gFoundHq.foundHq}=1;`,
`${gLostHq.foundHq}=1;`,
),
);
},
Expand All @@ -272,12 +272,12 @@ const WITH_FIND_OBJECTIVE: Pick<
const BASE: Omit<PartialArchitect<HqMetadata>, "prime"> &
Pick<Architect<HqMetadata>, "rough" | "roughExtent"> = {
...DefaultCaveArchitect,
...new RoughOyster(
...mkRough(
{ of: Rough.ALWAYS_FLOOR, width: 2, grow: 2 },
{ of: Rough.FLOOR, width: 0, grow: 2 },
{ of: Rough.DIRT, width: 0, grow: 0.5 },
{ of: Rough.DIRT_OR_LOOSE_ROCK, grow: 0.25 },
{ of: Rough.HARD_ROCK, grow: 0.25 },
{ of: Rough.MIX_LOOSE_HARD_ROCK, grow: 0.25 },
),
crystalsFromMetadata: (metadata) => metadata.crystalsInBuildings,
placeRechargeSeam: getPlaceRechargeSeams(1),
Expand Down Expand Up @@ -332,3 +332,5 @@ export const ESTABLISHED_HQ = [
...WITH_FIND_OBJECTIVE,
},
] as const satisfies readonly Architect<HqMetadata>[];

export default ESTABLISHED_HQ;
117 changes: 117 additions & 0 deletions src/core/architects/fissure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Architect, BaseMetadata } from "../models/architect";
import { DefaultHallArchitect, PartialArchitect } from "./default";
import { mkRough, Rough } from "./utils/rough";
import {
escapeString,
eventChain,
mkVars,
scriptFragment,
transformPoint,
} from "./utils/script";
import { DiscoveredCavern } from "../transformers/03_plastic/01_discover";
import { Plan } from "../models/plan";
import { monsterSpawnScript } from "./utils/creature_spawners";
import { Hardness, Tile } from "../models/tiles";

// Fissure halls are not drillable, but suddenly crack open without any player
// involvement after being discovered.

const METADATA = { tag: "fissure" } as const satisfies BaseMetadata;

function getDiscoveryPoints(cavern: DiscoveredCavern, plan: Plan<any>) {
const used: true[] = [];
return plan.innerPearl[0].filter((pos) => {
const dz = cavern.discoveryZones.get(...pos);
if (!dz || dz.openOnSpawn || used[dz.id]) {
return false;
}
used[dz.id] = true;
return true;
});
}

const sVars = (plan: Plan<any>) =>
mkVars(`p${plan.id}Fissure`, [
"onDiscover",
`onTrip`,
`msgForeshadow`,
`spawn`,
"tripCount",
]);

const BASE: PartialArchitect<typeof METADATA> = {
...DefaultHallArchitect,
prime: () => METADATA,
script: ({ cavern, plan }) => {
const v = sVars(plan);
const discoveryPoints = getDiscoveryPoints(cavern, plan);
const panTo = plan.innerPearl[0][Math.floor(plan.innerPearl[0].length / 2)];
const rng = cavern.dice.script(plan.id);

const drillPoints = plan.innerPearl[0].filter((pos) => {
const t = cavern.tiles.get(...pos) ?? Tile.SOLID_ROCK;
return t.isWall && t.hardness < Hardness.SOLID;
});
const trips = Math.ceil((discoveryPoints.length + drillPoints.length) / 4);

return scriptFragment(
`# P${plan.id}: Fissure`,
`int ${v.tripCount}=0`,
`string ${v.msgForeshadow}="${escapeString(cavern.lore.generateSeismicForeshadow(rng).text)}"`,
...discoveryPoints.map(
(pos) => `if(change:${transformPoint(cavern, pos)})[${v.onTrip}]`,
),
...drillPoints.map(
(pos) => `if(drill:${transformPoint(cavern, pos)})[${v.onTrip}]`,
),
eventChain(
v.onTrip,
`${v.tripCount}+=1;`,
`((${v.tripCount}!=${trips}))return;`,
`wait:random(5)(30);`,
`shake:1;`,
`msg:${v.msgForeshadow};`,
`wait:random(30)(150);`,
`shake:2;`,
`pan:${transformPoint(cavern, panTo)};`,
`wait:1;`,
`shake:4;`,
...plan.innerPearl[0]
.filter((pos) => cavern.tiles.get(...pos)?.isWall)
.map(
(pos) => `drill:${transformPoint(cavern, pos)};` as `${string};`,
),
cavern.context.hasMonsters && `${v.spawn};`,
),
);
},
monsterSpawnScript: (args) => {
const bps = args.plan.path.baseplates;
const ebps = [bps[0], bps[bps.length - 1]];
return monsterSpawnScript(args, {
armEvent: sVars(args.plan).spawn,
emerges: ebps.map((bp) => {
const [x, y] = bp.center;
return { x: Math.floor(x), y: Math.floor(y), radius: bp.pearlRadius };
}),
maxTriggerCount: 1,
triggerOnFirstArmed: true,
});
},
};

const FISSURE = [
{
name: "Fissure Hall",
...BASE,
...mkRough({ of: Rough.SOLID_ROCK }, { of: Rough.VOID, grow: 1 }),
hallBid: ({ plan, plans }) =>
!plan.fluid &&
plan.path.kind === "auxiliary" &&
plan.path.exclusiveSnakeDistance > 1 &&
!plan.intersects.some((_, i) => plans[i].metadata?.tag === "fissure") &&
1,
},
] as const satisfies readonly Architect<typeof METADATA>[];

export default FISSURE;
29 changes: 17 additions & 12 deletions src/core/architects/flooded.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Architect } from "../models/architect";
import { Tile } from "../models/tiles";
import { DefaultCaveArchitect, PartialArchitect } from "./default";
import { Rough, RoughOyster, weightedSprinkle } from "./utils/oyster";
import { mkRough, Rough, weightedSprinkle } from "./utils/rough";
import { intersectsOnly, isDeadEnd } from "./utils/intersects";
import { monsterSpawnScript } from "./utils/creature_spawners";
import { sprinkleCrystals } from "./utils/resources";
Expand All @@ -25,19 +25,24 @@ const FLOODED = [
{
name: "Lake",
...BASE,
...new RoughOyster(
...mkRough(
{ of: Rough.WATER, grow: 2 },
{ of: Rough.FLOOR, shrink: 1, grow: 1 },
{ of: Rough.LOOSE_ROCK },
{ of: Rough.LOOSE_OR_HARD_ROCK },
{
of: weightedSprinkle(
{ item: Rough.LOOSE_ROCK, bid: 10 },
{ item: Rough.LOOSE_OR_HARD_ROCK, bid: 1 },
),
},
),
caveBid: ({ plan }) =>
plan.fluid === Tile.WATER && plan.pearlRadius < 10 && 1,
},
{
name: "Lake With Sleeping Monsters",
...BASE,
...new RoughOyster(
...mkRough(
{ of: Rough.WATER, grow: 2 },
{ of: Rough.FLOOR, grow: 1 },
{ of: Rough.LOOSE_ROCK },
Expand All @@ -60,7 +65,7 @@ const FLOODED = [
{
name: "Island",
...BASE,
...new RoughOyster(
...mkRough(
{ of: Rough.ALWAYS_SOLID_ROCK, width: 0, grow: 0.7 },
{ of: Rough.ALWAYS_HARD_ROCK, width: 0, grow: 0.2 },
{ of: Rough.ALWAYS_LOOSE_ROCK, width: 0, grow: 0.2 },
Expand All @@ -69,15 +74,15 @@ const FLOODED = [
{ of: Rough.WATER, grow: 2 },
{ of: Rough.FLOOR, grow: 1 },
{ of: Rough.LOOSE_ROCK },
{ of: Rough.LOOSE_OR_HARD_ROCK },
{ of: Rough.MIX_FRINGE },
),
caveBid: ({ plan }) =>
plan.fluid === Tile.WATER && plan.pearlRadius > 5 && 2,
},
{
name: "Lava Lake",
...BASE,
...new RoughOyster(
...mkRough(
{ of: Rough.LAVA, grow: 2 },
{ of: Rough.FLOOR, grow: 1 },
{ of: Rough.LOOSE_ROCK, shrink: 1 },
Expand All @@ -89,7 +94,7 @@ const FLOODED = [
{
name: "Lava Island",
...BASE,
...new RoughOyster(
...mkRough(
{ of: Rough.ALWAYS_SOLID_ROCK, width: 0, grow: 0.7 },
{ of: Rough.ALWAYS_HARD_ROCK, width: 0, grow: 0.2 },
{ of: Rough.ALWAYS_LOOSE_ROCK, width: 0, grow: 0.2 },
Expand All @@ -105,15 +110,15 @@ const FLOODED = [
{
name: "Peninsula",
...BASE,
...new RoughOyster(
...mkRough(
{ of: Rough.ALWAYS_SOLID_ROCK, width: 0, grow: 0.7 },
{ of: Rough.ALWAYS_HARD_ROCK, width: 0, grow: 0.2 },
{ of: Rough.ALWAYS_LOOSE_ROCK, width: 0, grow: 0.2 },
{ of: Rough.ALWAYS_DIRT, width: 0, grow: 0.5 },
{ of: Rough.ALWAYS_FLOOR, width: 2, grow: 0.1 },
{ of: Rough.BRIDGE_ON_WATER, grow: 2 },
{ of: Rough.LOOSE_ROCK },
{ of: Rough.AT_MOST_HARD_ROCK },
{ of: Rough.MIX_FRINGE },
),
caveBid: ({ plans, plan }) =>
plan.fluid === Tile.WATER &&
Expand All @@ -125,7 +130,7 @@ const FLOODED = [
{
name: "Lava Peninsula",
...BASE,
...new RoughOyster(
...mkRough(
{ of: Rough.ALWAYS_SOLID_ROCK, width: 0, grow: 0.7 },
{ of: Rough.ALWAYS_HARD_ROCK, width: 0, grow: 0.2 },
{ of: Rough.ALWAYS_LOOSE_ROCK, grow: 0.2 },
Expand All @@ -144,7 +149,7 @@ const FLOODED = [
name: "Lava Stalagmite Cave",
...BASE,
crystalsToPlace: ({ plan }) => plan.crystalRichness * plan.perimeter * 2,
...new RoughOyster(
...mkRough(
{
of: weightedSprinkle(
{ item: Rough.ALWAYS_DIRT, bid: 0.01 },
Expand Down
21 changes: 15 additions & 6 deletions src/core/architects/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
import { Architect } from "../models/architect";
import { ESTABLISHED_HQ } from "./established_hq";
import ESTABLISHED_HQ, { HqMetadata } from "./established_hq";
import FISSURE from "./fissure";
import FLOODED from "./flooded";
import LOST_MINERS from "./lost_miners";
import NOMAD_SPAWN from "./nomads";
import LOOPBACK from "./loopback";
import LOST_MINERS, { LostMinersMetadata } from "./lost_miners";
import NOMAD_SPAWN, { NomadsMetadata } from "./nomads";
import SIMPLE_CAVE from "./simple_cave";
import SIMPLE_HALL from "./simple_hall";
import SIMPLE_SPAWN from "./simple_spawn";
import SLUGS from "./slugs";
import THIN_HALL from "./thin_hall";
import TREASURE from "./treasure";

export type AnyMetadata =
| undefined
| HqMetadata
| LostMinersMetadata
| NomadsMetadata
| { tag: "fissure" | "slugNest" | "treasure" };

export const ARCHITECTS = [
...ESTABLISHED_HQ,
...FISSURE,
...FLOODED,
...LOOPBACK,
...LOST_MINERS,
...NOMAD_SPAWN,
...SIMPLE_CAVE,
Expand All @@ -21,6 +32,4 @@ export const ARCHITECTS = [
...SLUGS,
...THIN_HALL,
...TREASURE,
] as const satisfies readonly Architect<any>[];

export type AnyMetadata = ReturnType<(typeof ARCHITECTS)[number]["prime"]>;
] as const satisfies readonly Architect<AnyMetadata>[];
Loading

0 comments on commit 5f296dc

Please sign in to comment.