-
-
Notifications
You must be signed in to change notification settings - Fork 0
Registries and Sampling
WorldgenLib exposes two registries for shared world data and a sampler for
queries that need the canonical terrain math. Register everything during
StartServerSide. The registries freeze at InitWorldGen.
LandformRegistry keeps vanilla and consumer landforms in one canonical index
space. It imports the vanilla variants, appends custom variants, initializes
their indexes for the active world height, and rebinds the list if vanilla
landforms are reloaded before the freeze.
| Member | Use |
|---|---|
Register(code, variant) |
Add a custom LandformVariant and receive its canonical index |
GetByCode(code) |
Find a variant by code, or null
|
GetIndex(code) |
Find a canonical index, or -1
|
GetThresholds(index) |
Read a cloned interpolated Y-threshold array |
SetThresholds(index, thresholds) |
Replace thresholds before the freeze |
GetTerrainOctaves(index) |
Read a cloned octave-amplitude array |
GetTerrainOctaveThresholds(index) |
Read a cloned octave-threshold array |
All |
Read all entries in canonical index order |
Landforms |
Access the active vanilla property after initialization |
Reload() |
Reload vanilla landforms and re-append registered custom variants |
Example lookup:
int riverIndex = LandformRegistry.GetIndex("game:riverlandform");
if (riverIndex < 0)
{
api.Logger.Warning("The configured river landform is not available.");
return;
}
float[] thresholds = LandformRegistry.GetThresholds(riverIndex);
for (int y = 0; y < thresholds.Length; y++)
thresholds[y] += GetRiverAdjustment(y);
LandformRegistry.SetThresholds(riverIndex, thresholds);To register a custom variant, build a fully populated LandformVariant using
the Vintage Story landform fields, then register it with a namespaced code:
LandformVariant variant = BuildMyLandformVariant();
int index = LandformRegistry.Register("my-mod:river-valley", variant);The variant must provide nonempty TerrainOctaves,
TerrainOctaveThresholds, TerrainYKeyPositions, and
TerrainYKeyThresholds. The two Y-key arrays must have the same length. The
registry assigns the canonical Code and initializes the variant with the
active world manager.
GetThresholds, GetTerrainOctaves, and GetTerrainOctaveThresholds return
clones. Changing a returned array does not change the active variant until
SetThresholds is called. SetThresholds requires finite values and, once the
world manager is known, exactly one value for every Y level in the world.
Call Reload() only before InitWorldGen freezes the registry. It is useful
when another system reloads the vanilla landform property during startup. A
reload after the freeze throws and requires a new worldgen session.
RegionMapRegistry allocates named IntDataMap2D slots attached to an
IMapRegion. Each slot declares its dimensions and format version up front.
The aggregate declaration budget is 4 MiB per region.
private static readonly RegionMapSlot StreamMap =
RegionMapRegistry.Register(
"my-mod",
"my-mod:stream-strength",
innerSize: 32,
padding: 2,
formatVersion: 1);
private static void OnMapsFinalized(RegionContext context)
{
IntDataMap2D map = context.GetMap(StreamMap);
map.Data[map.TopLeftPadding] = ComputeStreamValue(context);
}TotalSize is InnerSize + 2 * Padding. The real region API is the
persistent path:
IntDataMap2D map = StreamMap.GetMap(
context.MapRegion, context.RegionX, context.RegionZ);
int[] replacement = CreateReplacement(map.Data.Length);
StreamMap.SetMap(
context.MapRegion, context.RegionX, context.RegionZ, replacement);GetMap(IMapRegion, ...) loads the namespaced moddata when the region is first
accessed and retains the map for that region. SetMap(IMapRegion, ...) clones
the supplied array, updates ModMaps, and marks the region dirty. The host
flushes loaded slots after the map region pass. Data is encoded with a magic
value, format version, total size, and element count. A mismatched or unknown
payload is rejected and a new map is created.
The coordinate-only overloads, GetMap(int regionX, int regionZ) and
SetMap(int regionX, int regionZ, ...), are compatibility helpers for
in-memory callers and tests. They do not persist data to a real IMapRegion.
Use the overloads that take IMapRegion for saved world data.
Use a unique, stable map code. The persistence key is namespaced as
worldgenlib:region-map:<map-code>, so changing a code creates a new map. A
changed layout requires a deliberate format-version migration. Do not use a
region map for one record per block.
TerrainSampler evaluates the canonical terrain pipeline at arbitrary world
coordinates without placing blocks. It uses request-local scratch state and
is safe to call from concurrent consumers after worldgen initialization.
int baseHeight = TerrainSampler.SampleBaseTerrainHeight(worldX, worldZ);
int height = TerrainSampler.SampleHeight(worldX, worldZ);
double threshold = TerrainSampler.SampleThreshold(worldX, worldZ, posY);
var points = TerrainSampler.SampleTerrainHeightsBatch(
new[] { (worldX, worldZ), (worldX + 1, worldZ) });Available methods:
| Method | Meaning |
|---|---|
SampleHeight |
Canonical terrain height at one position |
SampleTerrainHeight |
Compatibility name for SampleHeight
|
SampleTerrainHeightsBatch |
Batch terrain heights keyed by input coordinate |
SampleBaseTerrainHeight |
Explicit unmodified canonical sample |
SampleBaseTerrainHeightsBatch |
Batch form of the base sample |
SampleThreshold |
Threshold value at a world X, Z, and Y |
InvalidateRegion |
Invalidate cached landform interpolation for a region |
SamplingModifiers can apply consumer-owned effects to a sample:
var modifiers = new SamplingModifiers
{
DistYDelta = erosionLift,
LandformWeightTransform = weights => ApplyRiverWeights(weights),
ThresholdTransform = (y, threshold) =>
y < seaLevel ? threshold - channelDepth : threshold
};
int estimatedHeight = TerrainSampler.SampleHeight(worldX, worldZ, modifiers);DistYDelta shifts vertical distortion. LandformWeightTransform receives a
sample-local array. ThresholdTransform receives the Y coordinate and the
current threshold. Keep transforms deterministic for a given seed and
coordinate.
The ignoreRivers parameter on SampleTerrainHeightsBatch remains for
compatibility with existing sampler call sites. WorldgenLib currently owns no
built-in river field, so the canonical base result is already river-neutral
and the flag has no separate effect.
The sampler throws InvalidOperationException before the WorldgenLib worldgen
host is initialized. It does not create blocks, update heightmaps, or persist
the result. Consumers that mutate maps must call InvalidateRegion before
sampling affected regions.