Repository navigation
api geography
Audience: Developers reading or writing what Blacksmith knows about a scene as a place.
Covers scene geography, the canonical habitat vocabulary, and how a scene's own values resolve
against the campaign defaults. Implementation lives in scripts/manager-geography.js.
Blacksmith stores four location fields, an habitat list, a reputation value and an optional link to
a Location journal on the Scene document, under the flag coffee-pub-blacksmith.geography.
const blacksmith = game.modules.get('coffee-pub-blacksmith')?.api;
blacksmith.geography.get(canvas.scene); // {realm, region, site, area}, resolved
blacksmith.geography.getHabitats(canvas.scene); // ['forest', 'hill']
blacksmith.geography.getSceneContext(canvas.scene); // everything, including reputation| Method | Returns | Purpose |
|---|---|---|
get(scene?) |
{realm, region, site, area} |
Resolved geography. With no scene, the campaign defaults. |
getSceneContext(scene?) |
object | Everything below, in one call. |
getHabitats(scene) |
string[] |
Canonical habitat keys. |
getBreadcrumb(scene?) |
string |
"Faerun > Sword Coast > Baldur's Gate", empty segments skipped. |
set(scene, data) |
Promise<boolean> |
Write any subset of the fields. GM only. |
clear(scene) |
Promise<boolean> |
Remove the flag entirely. GM only. |
normalizeHabitats(value) |
string[] |
Canonicalise a raw habitat array. |
HABITATS and HABITAT_KEYS are also exposed on api.geography.
getSceneContext returns exactly these keys: realm, region, site, area, habitat,
reputation, locationUuid.
The four campaign settings (defaultCampaignRealm and its siblings) are the seed for a scene that
has not been told otherwise, not live state. Resolution is per field: a scene that sets only realm
inherits the other three.
An empty string means inherit, not deliberately blank. Clearing a field in the UI returns it to the campaign default rather than storing an empty segment — the placeholder in the Geography tab shows which default would apply.
Twelve values, a closed constant rather than a registry, exposed as {key, label} pairs:
mountain, arctic, planar, coastal, swamp, desert, underdark, forest, underwater,
grassland, urban, hill.
The key is what you store and join on. The label is what a person reads. They are separate because a value used as both an identity and a visible string cannot be made human-readable without breaking whatever round-trips it.
Normalise at your boundary rather than trusting the stored form. normalizeHabitats accepts any
case, drops anything outside the vocabulary, collapses duplicates, and returns vocabulary order so two
scenes with the same habitats compare equal.
Two consequences worth knowing before you render or match on the result:
-
The order is vocabulary order, not alphabetical. It is stable and comparable, but a consumer that
previously sorted and now renders
getHabitats()directly will show a different order than before, with nothing to signal the change. Sort at your own display layer if the order matters to a reader. - A value outside the twelve resolves to nothing. Filtering is against the closed vocabulary rather than for truthiness, so a hand-edited flag carrying a bespoke habitat stops matching rather than passing through. That is intended -- an unknown habitat cannot mean anything to a consumer keyed on the twelve -- but it is a behaviour change for anything that used to accept whatever it found. Values written by a hand-edited flag or by an older build are otherwise a silent join failure.
It also drops null, which matters more than it sounds. A checkbox group submits one entry per box with
null for each unticked one, and String(null) is "null" — truthy, so a filter(Boolean) normaliser
stores the literal string "null" once per box, producing data that looks populated and matches nothing.
See documentation/api/api-scene-config.md for the mechanism.
reputation is -100..100. A missing key means never set and reads as null; 0 means neutral.
These are different answers and an aggregate that treats them alike drags toward zero. Values outside the
scale are clamped on write.
set takes any subset of the fields and merges. Everything is normalised on the way in, so a caller
cannot store an habitat outside the vocabulary or a reputation outside the scale.
await blacksmith.geography.set(scene, { realm: 'Faerun', habitat: ['forest'] });The stored value is always canonical, whoever wrote it. The Scene Config tab does not go through
set -- Foundry's form submission writes its inputs directly -- and a checkbox group submits one entry
per box with null for each unticked one. A preUpdateScene hook normalises the flag on its way onto
the document, so habitat never reaches the scene with nulls in it, the four fields are trimmed, and
reputation is clamped. That matters for anything reading the raw flag rather than going through this API:
a sibling module, a scene exported to a compendium, or a person looking at it in a console.
Both set and clear require GM and return false otherwise. The Geography tab in Scene Config does
not use them: its inputs are named flags.coffee-pub-blacksmith.geography.<field>, so Foundry's own form
submission persists them.
- Ai assistance
- Applicationv2 traps
- CSS traps
- dnd5e conditions
- Documentation standard
- Token actor identity
- Campaign
- Canvas
- Chatcards
- Compendiums
- Contextmenu
- Core
- Create journal entry
- Dialog
- Active Effects
- Entity list
- Equiplocations
- Geography
- GM request
- Gmnotes
- Health
- Hookmanager
- Importer
- Inventory
- Menubar
- Notes
- Party
- Pins
- Quantity split
- Requestroll
- Rolls
- Scene config
- Sockets
- Stats
- Tags
- Toast
- Tokens
- Toolbar
- Window
- Worldclock