MUD Procedural generation: outlining city walls

This starts a series on procedural generation for a MUD, which is a text-only MMO, or a multiplayer Zork. The eventual goal is to create a large, varied MUD containing multiple cities, towns, and wilderness biomes, and a wide array of NPCs and items, as the basis for more personalized and customized work.

Today we’re looking at generating the outline of a city: the city walls.

In real life, a walled city can have almost any shape, but some shapes are more defensible than others. And defense is the sole reason to add walls to the city. A late invention was the star bastion, but before that, walled cities seem to have been mostly convex hulls, or not far from it. That is, they tend not to have significant inward angles in the walls.

Also, walled cities tend to have towers and other fortifications where the walls change directions.

We’re going to use both these features.

In our first step, we’re going to choose a number of primary towers and a radius and variance for our city — in this case, we’ve hard-coded it to a circular shape, and later we can add support for rectangular and elliptical variants.

// Choose the size of the city.
auto radius = uniform(60, 100, rnd);
auto rVariance = radius / 10;

// How many towers do we want?
auto segments = uniform!"[]"(4, 8, rnd);
// What angle does each command?
auto region = PI * 2 / segments;

Each tower commands an equal slice of the city wall, and each tower is randomly placed within that slice:

Point[] towers;
for (int i = 0; i < segments; i++) {
  auto start = region * i;
  auto end = region * (i + 1);
  auto angle = uniform!"[]"(start, end, rnd);
  auto dist = uniform!"[]"(radius - rVariance, radius + rVariance, rnd);
  auto tower = toCoords(angle, dist);
  towers ~= tower;
}

For instance, we might choose a radius of 80, which means the towers of the city will be between 72 and 88 rooms from the center point. And we choose six towers, so even in a particularly devolved case, our city will be a triangle (if adjacent pairs of towers are located as close as possible). If we picked the minimum, four towers, we could get a double-thickness wall and no city! But we can detect that later and filter those out.

Now, those towers are in each Kdrant (I don't know if that's a word -- quadrant, but for some K instead of a constant 4). And it's one per Kdrant. That might be a little unnatural in its evenness, so we'll just add a few other random towers:

auto extraTowers = uniform!"[]"(2, 3, rnd);
for (int i = 0; i < extraTowers; i++) {
  auto angle = uniform!"[]"(0, PI * 2, rnd);
  auto dist = uniform!"[]"(radius - rVariance, radius + rVariance, rnd);
  towers ~= toCoords(angle, dist);
}

And now that we have the towers, we can add them to the work-in-progress map:

auto rooms = Cube!Entity(radius + rVariance);
foreach (i, tower; towers) {
  for (long height = 0; height < tower.height; i++) {
    rooms.addRoom(Point(tower.x, tower.y, height), "Tower");
  }
}

It's just empty rooms named "Tower" right now, and we still need to connect the lower levels of the tower to the upper levels, but this is a reasonable start.

Next time we'll have a look at drawing walls.

Leave a Reply