-
-
Notifications
You must be signed in to change notification settings - Fork 0
Example River
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.
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.
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.
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.