Enumerating Terrain

Overview

In order to add terrain to UAW, it must be enumerated. This is done via newTerrain() in the UAW_API_Extension interface during extension initialization.

UAW cannot load terrains, only scenarios – so this example enumerates a scenario, too.

  1. Enumerate a terrain.
  2. Enumerate a scenario that uses this terrain.
  3. Implement the UAW_Extension_Terrain interface for the terrain.

Prerequisites

Start with the sample extension project.

Both the new terrain and the new scenario will need an ID. The scenario will also need a display name and a description:

#include <UAW API.h>

enum TerrainID : UAW_Extension_TerrainID {
	sampleTerrain = 123, // choose any number you like
};

enum ScenarioID : UAW_Extension_ScenarioID {
	sampleScenario = 123, // choose any number you like
};

constexpr auto tileSize = 2048.f; // ~2 km – good speed/space tradeoff

enum StringID : UAW_Extension_StringID {
	extensionName,
	extensionAuthor,
	extensionUrl,
	extensionDescription,
	sampleScenarioName,
	sampleScenarioDescription,
};

// Returns a string in UAW’s current language.
UAW_Char const * UAW_CALL stringFromStringID(
	UAW_Extension const *  extension, // explained later
	UAW_Extension_StringID id
) {
	switch(id) {
		case extensionName:
			return u"Sample Extension";
		case extensionAuthor:
			return u"Someone";
		case extensionUrl:
			return u"http://example.com";
		case extensionDescription:
			return u"Demonstrates the implementation of a basic UAW extension.";
		case sampleScenarioName:
			return u"Sample Scenario";
		case sampleScenarioDescription:
			return u"Demonstrates the implementation of a basic UAW scenario.";
		default:
			return u"YOU FORGOT TO DEFINE THIS STRING!";
	}
}

Enumerating Terrain

In the extension initialization function, fill an instance of the UAW_TerrainSpecs structure and pass it to the newTerrain() API function.

This requires a callback for loading the terrain. For the moment, just create an empty function that never does anything; it will be filled later.

UAW_Extension_Terrain * UAW_CALL loadTerrain(
	UAW_Extension *             extension,
	UAW_API_TerrainLoad *       uaw,
	UAW_Extension_TerrainID     id,
	UAW_Extension_Terrain_API * result
) {
	return nullptr; // for now, simulate failure
}

The UAW_TerrainSpecs structure describes the terrain that is being enumerated. Hence, it must communicate the terrain’s ID.

The geographical origin is expressed as two single-precision floating-point numbers. latitude is in [-90, +90] range (with North being positive), and longitude in [-180, +180] range (with East being positive). Since all terrain must be North-East of the origin, this example picks a place in the Celtic Sea (49°N/10°W) – thus allowing to map Great Britain.

A tile sidelength of 2 km is a good default for speed/space tradeoffs.

extern "C" __declspec(dllexport) int UAW_CALL UAW_EXTENSION_CREATE(
	UAW_API_Extension *       uaw,
	UAW_Extension_Callbacks * result,
	void *                    legacyPleaseIgnore
) {

[…]

	result->string      = &stringFromStringID;
	result->name        = extensionName;
	result->author      = extensionAuthor;
	result->url         = extensionUrl;
	result->description = extensionDescription;

	// Declare sample terrain:
	UAW_TerrainSpecs terrain = { };
	terrain.id             = sampleTerrain;
	terrain.latitude       = 49.f; // origin is somewhere in the Celtic Sea
	terrain.longitude      = -10.f;
	terrain.tileSidelength = tileSize;
	terrain.load           = &loadTerrain; // pass our callback
	uaw->newTerrain(terrain);

The callbacks must remain available to UAW throughout the entire runtime of the process! If you use a garbage-collected or interpreted language, mind memory pinning!

All of the above could be pre-computed at compile-time via constexpr, saving execution time and code size. The example omits such optimization for clarity.

Enumerating A Scenario

The terrain is now known to UAW, but a scenario is required to make use of it.

Scenario enumeration, just like terrain enumeration, requires a callback for populating the scenario with targets. For now, stick to an empty function:

UAW_Extension_Scenario * UAW_CALL createScenario(
	UAW_Extension *              extension,
	UAW_API_ScenarioPopulate *   uaw,
	UAW_Extension_ScenarioID     id,
	UAW_Extension_Scenario_API * methods
) {
	return nullptr; // for now, simulate failure
}

After calling newTerrain(), fill an instance of the UAW_ScenarioSpecs structure and pass it to the newScenario() API function.

The ordering is important here because newScenario checks the terrain ID for validity. It would abort the program with an error if the terrain were not yet enumerated via newTerrain()!

	uaw->newTerrain(terrain);

	// Declare an empty scenario using the sample terrain:
	UAW_ScenarioSpecs scenario = { };
	scenario.id        = sampleScenario;
	scenario.terrainID = sampleTerrain;
	scenario.name      = sampleScenarioName;
	scenario.summary   = sampleScenarioDescription;
	scenario.create    = &createScenario; // pass our callback
	uaw->newScenario(scenario);

UAW_ScenarioSpecs has far more fields, e.g. for date/time and weather. These will be handled later.

Again, all of the above could be pre-computed at compile-time via constexpr.

The scenario is now listed in UAW:

UAW displaying SampleScenario in its scenario list.

Note that it will not start because loadTerrain() simulates failure:

UAW displaying SampleScenario in its scenario list.

This will be handled in the next chapter.

Download an updated sample project here.