Skip to content

Commit

Permalink
squash buildandpower
Browse files Browse the repository at this point in the history
  • Loading branch information
charredUtensil committed Sep 15, 2024
1 parent 7cb328b commit d43e5ae
Show file tree
Hide file tree
Showing 19 changed files with 389 additions and 170 deletions.
213 changes: 213 additions & 0 deletions src/core/architects/build_and_power.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
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";
import { Plan } from "../models/plan";
import { OrderedOrEstablishedPlan } from "../transformers/01_planning/05_establish";
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";

const TAG = 'buildAndPower' as const;
export type BuildAndPowerMetadata = {
readonly tag: typeof TAG;
readonly template: Building['template'];
};

const BASE: PartialArchitect<BuildAndPowerMetadata> = {
...DefaultCaveArchitect,
maxSlope: 15,
};

function buildAndPower(
template: Building['template'],
minLevel: Building['level'] = 1,
): Pick<Architect<BuildAndPowerMetadata>, "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<any>) => 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;
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('')
}],
sufficient: true,
}
},
scriptGlobals({cavern}) {
const pvs = cavern.plans
.filter(plan => plan.metadata?.tag === TAG && plan.metadata.template === template)
.map(plan => mv(plan));
return scriptFragment(
`# Globals: Build and Power ${template.name}`,

// First trigger: when building is built or leveled up. This doesn't
// work with mutexes properly, but this is the least likely event to
// 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}]`,
eventChain(
g.onBuild,
`savebuilding:${g.built};`,
minLevel > 1 && `((${g.built}.level<${minLevel}))return;`,
...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}`),
`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};`[]),
),

// 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, {}, {
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;`,
),
);
},
script({cavern, plan}) {
const v = mv(plan);
if (plan.path.baseplates.length > 1) {
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);
if (!arrowPos) {
throw new Error('No non-wall points found');
}
const atp = transformPoint(cavern, arrowPos);
const openOnSpawn = cavern.discoveryZones.get(...arrowPos)!.openOnSpawn;

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.onBuild,
// Filter out buildings outside the baseplate rectangle
`((${g.built}.column<${bp.left - cavern.left}))return;`,
`((${g.built}.column>=${bp.right - cavern.left}))return;`,
`((${g.built}.row<${bp.top - cavern.top}))return;`,
`((${g.built}.row>=${bp.bottom - cavern.top}))return;`,
`savebuilding:${v.building};`,
`${g.onPower};`,
),
);
}
}
}

function bidHelper(
plans: readonly OrderedOrEstablishedPlan[],
template: Building['template'],
max: number,
dormant: number,
active: number,
): number | false {
let count = 0;
for (const p of plans) {
if (p.metadata?.tag === TAG) {
if (p.metadata.template !== template) {
return false;
}
count += 1;
if (count >= max) {
return false;
}
}
}
return count > 0 ? active : dormant;
}

export const BUILD_AND_POWER = [
{
name: "BuildAndPower.GeologicalCenter",
...BASE,
...buildAndPower(GEOLOGICAL_CENTER, 5),
...mkRough(
{ of: Rough.FLOOR, width: 2, 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;
}))
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.1, 10);
},
}
] as const satisfies readonly Architect<BuildAndPowerMetadata>[]
28 changes: 15 additions & 13 deletions src/core/architects/established_hq/fixed_complete.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { inferContextDefaults } from "../../common";
import { FAILURE_BASE_DESTROYED } from "../../lore/graphs/events";
import { LoreDie } from "../../lore/lore";
import { Architect } from "../../models/architect";
import {
TOOL_STORE,
Expand All @@ -12,7 +14,7 @@ import {
ALL_BUILDINGS,
} from "../../models/building";
import {
escapeString,
declareStringFromLore,
eventChain,
mkVars,
scriptFragment,
Expand All @@ -34,7 +36,7 @@ const T0_BUILDINGS = [

const T0_CRYSTALS = T0_BUILDINGS.reduce((r, bt) => r + bt.crystals, 0);

const gFixedCompleteHq = mkVars("gFCHQ", [
const gFCHQ = mkVars("gFCHQ", [
"onInit",
"onBaseDestroyed",
"msgBaseDestroyed",
Expand Down Expand Up @@ -65,23 +67,23 @@ export const FC_BASE: Pick<
scriptGlobals: ({ cavern }) => {
return scriptFragment(
`# Globals: Fixed Complete HQ`,
`if(time:0)[${gFixedCompleteHq.onInit}]`,
`if(time:0)[${gFCHQ.onInit}]`,
eventChain(
gFixedCompleteHq.onInit,
gFCHQ.onInit,
// Can't just disable buildings because that disables fences - and
// nobody wants that.
...ALL_BUILDINGS.map((bt) => `disable:${bt.id};` as `${string};`),
),
`string ${gFixedCompleteHq.msgBaseDestroyed}="${escapeString(cavern.lore.generateFailureBaseDestroyed(cavern.dice).text)}"`,
`int ${gFixedCompleteHq.wasBaseDestroyed}=0`,
`if(${TOOL_STORE.id}<=0)[${gFixedCompleteHq.onBaseDestroyed}]`,
`if(${POWER_STATION.id}<=0)[${gFixedCompleteHq.onBaseDestroyed}]`,
`if(${SUPPORT_STATION.id}<=0)[${gFixedCompleteHq.onBaseDestroyed}]`,
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}]`,
`if(${SUPPORT_STATION.id}<=0)[${gFCHQ.onBaseDestroyed}]`,
eventChain(
gFixedCompleteHq.onBaseDestroyed,
`((${gFixedCompleteHq.wasBaseDestroyed}>0))return;`,
`${gFixedCompleteHq.wasBaseDestroyed}=1;`,
`msg:${gFixedCompleteHq.msgBaseDestroyed};`,
gFCHQ.onBaseDestroyed,
`((${gFCHQ.wasBaseDestroyed}>0))return;`,
`${gFCHQ.wasBaseDestroyed}=1;`,
`msg:${gFCHQ.msgBaseDestroyed};`,
`wait:5;`,
`lose;`,
),
Expand Down
9 changes: 4 additions & 5 deletions src/core/architects/established_hq/lost.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { FOUND_HQ } from "../../lore/graphs/events";
import { LoreDie } from "../../lore/lore";
import { Architect } from "../../models/architect";
import { getDiscoveryPoint } from "../utils/discovery";
import { placeLandslides } from "../utils/hazards";
import {
DzPriorities,
scriptFragment,
mkVars,
escapeString,
transformPoint,
eventChain,
declareStringFromLore,
} from "../utils/script";
import { BASE, HqMetadata, getPlaceBuildings, getPrime } from "./base";

Expand Down Expand Up @@ -49,13 +51,10 @@ const LOST_BASE: Pick<
}).center;

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

return scriptFragment(
`# P${plan.id}: Lost HQ`,
`string ${v.messageDiscover}="${escapeString(message)}"`,
shouldPanMessage && declareStringFromLore(cavern, LoreDie.foundHq, v.messageDiscover, FOUND_HQ, {}, {}),
`if(change:${transformPoint(cavern, discoPoint)})[${v.onDiscover}]`,
eventChain(
v.onDiscover,
Expand Down
8 changes: 4 additions & 4 deletions src/core/architects/fissure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Architect, BaseMetadata } from "../models/architect";
import { DefaultHallArchitect, PartialArchitect } from "./default";
import { mkRough, Rough } from "./utils/rough";
import {
escapeString,
declareStringFromLore,
eventChain,
mkVars,
scriptFragment,
Expand All @@ -12,6 +12,7 @@ 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";
import { SEISMIC_FORESHADOW } from "../lore/graphs/seismic";

// Fissure halls are not drillable, but suddenly crack open without any player
// involvement after being discovered.
Expand Down Expand Up @@ -57,7 +58,7 @@ const BASE: PartialArchitect<typeof METADATA> = {
return scriptFragment(
`# P${plan.id}: Fissure`,
`int ${v.tripCount}=0`,
`string ${v.msgForeshadow}="${escapeString(cavern.lore.generateSeismicForeshadow(rng).text)}"`,
declareStringFromLore(cavern, rng, v.msgForeshadow, SEISMIC_FORESHADOW, {}, {}),
...discoveryPoints.map(
(pos) => `if(change:${transformPoint(cavern, pos)})[${v.onTrip}]`,
),
Expand All @@ -66,8 +67,7 @@ const BASE: PartialArchitect<typeof METADATA> = {
),
eventChain(
v.onTrip,
`${v.tripCount}+=1;`,
`((${v.tripCount}!=${trips}))return;`,
`((${v.tripCount}==${trips-1}))[${v.tripCount}+=1][return];`,
`wait:random(5)(30);`,
`shake:1;`,
`msg:${v.msgForeshadow};`,
Expand Down
3 changes: 3 additions & 0 deletions src/core/architects/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Architect } from "../models/architect";
import { BUILD_AND_POWER, BuildAndPowerMetadata } from "./build_and_power";
import ESTABLISHED_HQ from "./established_hq";
import { HqMetadata } from "./established_hq/base";
import FISSURE from "./fissure";
Expand All @@ -15,12 +16,14 @@ import TREASURE from "./treasure";

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

export const ARCHITECTS = [
...BUILD_AND_POWER,
...ESTABLISHED_HQ,
...FISSURE,
...FLOODED,
Expand Down
Loading

0 comments on commit d43e5ae

Please sign in to comment.