Color Utils

The game’s colour model.

Colours are not stored as RGB but as a 16 bit hue, saturation and luminance index into a palette built once at startup - see TColorUtils.PackHSL and TColorUtils.CreateHSVPalette. The global ColorUtils holds those palettes.

There is one palette, and everything draws through it - the map renderer and the sprite rasterizer both read ColorUtils.Palette rather than carrying one of their own. Its entries are already in the order the PColorBGRA buffer TImage.FromData reads, with the alpha byte full, so neither has to convert a pixel on the way out. It is built by TColorUtils.Setup at whatever brightness you ask for and stays put; TColorUtils.SetBrightness rebuilds it if you want a different look.


ColorUtils.PackHSL

function TColorUtils.PackHSL(hue, saturation, luminance: Integer): Integer; static;

Packs a colour into the 16 bit index the game’s palettes are addressed by: 6 bits of hue, 3 of saturation and 7 of luminance.

Saturation is halved at each of four luminance thresholds on the way up, because a colour that bright cannot carry much saturation without looking wrong - the same taper the client applies.


ColorUtils.CreateHSVPalette

function TColorUtils.CreateHSVPalette(brightness: Double; mn, mx: Integer): TIntegerArray; static;

Builds the 65536 entry palette that a TColorUtils.PackHSL index looks up.

Every index is unpacked back into hue, saturation and luminance, converted to RGB, and then bent by brightness - a gamma exponent, where a lower number means a brighter image. The client offers a few fixed brightness settings, which are the BRIGHTNESS_ constants.

Entries are alpha, red, green and blue from the top byte down, which is what the PColorBGRA buffer TImage.FromData reads - not a TColor, which has red and blue the other way round.

mn and mx bound which entries are actually computed; the rest are left at 0.


ColorUtils.SetBrightness

procedure TColorUtils.SetBrightness(brightness: Double);

Rebuilds both palettes at a new brightness. Everything that draws - the map renderer and the sprite rasterizer alike - reads them, so this changes the whole library’s colours at once.

Palette is the plain lookup a packed HSL index goes through. MinimapPalette is that same lookup with the flatter minimap lighting already folded into it, so that TColorUtils.UnderlayColor and TColorUtils.OverlayColor are a single array read rather than a rescale and a clamp per tile.

Both are in the order TColorUtils.CreateHSVPalette produces - alpha, red, green, blue - which is what the PColorBGRA buffer TImage.FromData reads, so whatever is drawn through them is handed to an image as it is.

Rebuilding costs around 25ms, nearly all of it the brightness curve, so this is meant for setting a look up front rather than for switching back and forth. Setting the brightness it already has does nothing.

Work already derived from the old palette is thrown away rather than left to go stale, but not from here - ColorUtils is defined long before anything that draws and there may be more than one map renderer. Instead Revision counts the changes, and TMapChunkLoader.EnsureColorsCurrent compares it against the revision its caches were built at and rebuilds them when they no longer match.


ColorUtils.UnderlayColor

function TColorUtils.UnderlayColor(hsl: Integer): Integer;

The colour a ground underlay is drawn as on the minimap.

The minimap is lit flatter than the world is, so the luminance in the index is scaled towards a fixed level before the palette is looked up. That rescale is already baked into MinimapPalette by TColorUtils.Setup, which leaves this a plain lookup. An hsl of -1 means the tile has no underlay.


ColorUtils.OverlayColor

function TColorUtils.OverlayColor(hsl: Integer): Integer;

TColorUtils.UnderlayColor for an overlay, which additionally distinguishes an overlay that is deliberately blank, -2, from one that is drawn at the flat minimap luminance, -1.


ColorUtils.Height2Color

function TColorUtils.Height2Color(height: Integer): Integer; static;

A terrain height as a shade of grey, black at ground level and white at the highest terrain the game uses. Heights are negative upwards, which is why the sign is flipped.


ColorUtils.SetupHeightColors

procedure TColorUtils.SetupHeightColors();

Fills HeightColors with TColorUtils.Height2Color for every height from ground level, index 0, up to MAX_HEIGHT. A height is negative upwards, so height h is at index -h.

Everything past either end is the same colour as that end, so a lookup only has to clamp the index rather than redo the division and rounding for every tile.


ColorUtils.Collision2Color

function TColorUtils.Collision2Color(flag: Integer): Integer; static;

A collision flag as a colour: white for walkable, black for blocked, grey for a tile an object stands on.