Skip to content

Example River

Zaldaryon edited this page Aug 28, 2026 · 1 revision

Example river consumer

WorldgenLib.ExampleRiver is a small reference consumer. It demonstrates a server-only mod that registers three effects on one pipeline: fresh-water selection, threshold carving, and post-placement water filling.

This sample is synthetic. It is not a migrated Rivers-Mod or VSRiverGen build. It is useful for checking the delegate signatures and the lifecycle position of each hook.

Complete example

using System;
using Vintagestory.API.Common;
using Vintagestory.API.Server;

namespace WorldgenLib.ExampleRiver;

public sealed class ExampleRiverMod : ModSystem
{
    private const string ModId = "example-river";
    private const int RiverCenterX = 0;
    private const int RiverHalfWidth = 3;
    private const int RiverDepth = 15;
    private const double ThresholdDelta = 0.3;

    public override bool ShouldLoad(EnumAppSide side)
        => side == EnumAppSide.Server;

    public override void StartServerSide(ICoreServerAPI api)
    {
        if (!WorldgenLibAPI.IsLoaded)
        {
            api.Logger.Warning("WorldgenLib is required by ExampleRiver.");
            return;
        }

        double order = OrderBands.AfterVanillaMin + 50;
        WorldgenLibAPI.RegisterStep5(ModId, order, SelectFreshWater);
        WorldgenLibAPI.RegisterStep7(ModId, order, CarveThreshold);
        WorldgenLibAPI.RegisterStep10(ModId, order, FillChannel);
    }

    private static bool InChannel(int worldX)
        => Math.Abs(worldX - RiverCenterX) <= RiverHalfWidth;

    private static void SelectFreshWater(
        ChunkContext chunk, ref ColumnContext column)
    {
        if (InChannel(column.WorldX))
            column.WaterBlockId = chunk.FreshWaterBlockId;
    }

    private static double CarveThreshold(
        ChunkContext chunk,
        ref ColumnContext column,
        int posY,
        double threshold)
    {
        if (!InChannel(column.WorldX)
            || posY >= Math.Max(1, chunk.SeaLevel - RiverDepth))
            return threshold;

        double distance = Math.Abs(column.WorldX - RiverCenterX);
        double normalized = distance / RiverHalfWidth;
        double falloff = 1.0 - normalized * normalized;
        return threshold - ThresholdDelta * falloff;
    }

    private static void FillChannel(
        ChunkContext chunk, ref ColumnCarvingContext column)
    {
        if (!InChannel(column.WorldX)) return;

        for (int y = 1; y < column.SeaLevel; y++)
        {
            if (!column.ColumnBlockSolidities[y])
            {
                column.SetFluid(
                    column.LocalX,
                    y,
                    column.LocalZ,
                    column.WaterBlockId);
            }
        }
    }
}

Step 5 runs once per column before the noise evaluation and changes the water block selected by the canonical terrain pass. Step 7 runs inside the vertical Y loop, so it is a hot callback. Step 10 runs after block placement and exposes all vertical chunks for global-Y carving or filling.

SetFluid takes local X and Z but global Y. It selects the vertical chunk that contains that Y level. This matters in worlds whose terrain request spans more than one 32-block vertical chunk and at negative world coordinates.

Building the sample

In the development source tree, build the library first and then the sample:

dotnet build WorldgenLib/WorldgenLib.VintageStory.csproj
dotnet build WorldgenLib.ExampleRiver/WorldgenLib.ExampleRiver.csproj

Copy the resulting consumer mod beside WorldgenLib in a server Mods folder. The public repository currently publishes the project documentation and API status. Source publication is a separate release step.

Adapting the pattern

Real river consumers usually need more state than this sample. Use Step 2 for landform blending, ChunkContext.CustomData for request-local state, TerrainFinalize for data written once after all columns, and a registered region map for persistent region-scale values. Keep the client rendering path in the consumer mod.

Clone this wiki locally