Modding:Zone Builders
Zone builders
Zone builders are classes in the XRL.World.ZoneBuilders namespace that are successively applied during the generation of a zone. Each builder operates on a Zone object and changes some attribute of it.
Builders can be added to a given zone by specifying <builder>...</builder> tags in Worlds.xml. For example, the following snippet from Worlds.xml is used to build the zone for Kyakukya and the surrounding zones:
<cell Name="Kyakukya" Inherits="Jungle" ApplyTo="TerrainKyakukya" Mutable="false">
  <zone Level="5-9" x="0-2" y="0-2" Name="sky above Kyakukya" IndefiniteArticle="the">
    <builder Class="Sky"></builder>
  </zone>
  <zone Level="10" x="0-2" y="0-2" Name="outskirts" NameContext="Kyakukya" IndefiniteArticle="some" IncludeStratumInZoneDisplay="false" HasWeather="true" WindSpeed="0-60" WindDirections="N,NW,NW,W,W,SW,S,SE" WindDuration="50-3000">
    <population Table="Kyakukya Outskirts"></population>
  </zone>
  <zone Level="10" x="1" y="1" Name="Kyakukya" ProperName="true" IncludeStratumInZoneDisplay="false" HasWeather="true" WindSpeed="0-60" WindDirections="N,NW,NW,W,W,SW,S,SE" WindDuration="50-3000">
    <map FileName="Kyakukya.rpm"></map>
    <builder Class="Music" Track="Kyakukya" Chance="100"></builder>
    <builder Class="IsCheckpoint" Key="Kyakukya"></builder>
  </zone>
</cell>
This snippet includes several zone builders, such as
- The Skybuilder[1] is used to generate the zones over Kyakukya. This builder effectively does nothing other than add open air to every tile in the zone.
- The Musicbuilder[2] sets the music track that gets placed in Kyakukya
- The IsCheckpointbuilder[3] registers Kyakukya as a checkpoint for the game to auto-save, for games played in Roleplay or Wander mode.
To determine the builders that were used to generate a zone in-game, you can use the zonebuilders wish.
Creating a custom zone builder
Suppose that you had a custom terrain type called MyTerrain (see Modding:Maps), which you wanted to behave like a salt marsh with a large pit in the center (similar to Rust Wells). You could start by creating a Worlds.xml with the following contents (borrowed from WatervineCell):
<?xml version="1.0" encoding="utf-8" ?>
<worlds>
  <world Name="JoppaWorld" Load="Merge">
    <cell Name="MyName_MyMod_MyCell" Inherits="WatervineCell" ApplyTo="MyTerrain" Mutable="false" >
      <zone Level="10" x="1" y="1" Name="salt marsh" HasWeather="true" WindSpeed="0-50" WindDirections="N,NW,NW,W,W,SW,S,SE" WindDuration="200-3000" AmbientBed="Sounds/Ambiences/amb_bed_marsh" AmbientSounds="amb_scatter_marsh_bird1,amb_scatter_marsh_bird2,amb_scatter_marsh_insect1,amb_scatter_marsh_insect2">
        <builder Class="Watervine"></builder>
        <builder Class="FactionEncounters" Population="SafeFactionPopulation"></builder>
        <builder Class="Music" Track="Overworld1" Chance="10"></builder>
        <builder Class="ZoneTemplate:SaltMarsh"></builder>
        <!-- This is new! -->
        <builder Class="YourName_YourMod_PitBuilder"></builder>
      </zone>
      <zone Level="11-15" x="1" y="1" Name="subterranean salt marsh">
        <builder Class="Strata"></builder>
        <builder Class="Watervine" Underground="true"></builder>
        <builder Class="ZoneTemplate:WatervineCaves"></builder>
        <builder Class="FactionEncounters" Population="SafeFactionPopulation"></builder>
        <builder Class="Music" Track="Overworld1" Chance="10"></builder>
        <!-- This is new! -->
        <builder Class="YourName_YourMod_PitBuilder"></builder>
      </zone>
    </cell>
  </world>
</worlds>
In each zone, we add a new zone builder, YourName_YourMod_PitBuilder, implemented as follows:
using Genkit;
namespace XRL.World.ZoneBuilders {
    /// <summary>
    /// Custom ZoneBuilder that creates a large pit in the center of the zone, similar to
    /// the Rust Wells.
    /// </summary>
    public class YourName_YourMod_PitBuilder : ZoneBuilderSandbox
    {
        public bool BuildZone(Zone Z)
        {
            // Get center of map
            Point2D center = Location2D.get(40, 12).point;
            // Radius of pit is dependent on depth: radius=10 at Z=10, radius=4 at Z=15
            // Radius at each intermediate floor is determined by linearly interpolating between
            // these two values.
            float startFloor = 10f, endFloor = 15f;
            float startRadius = 10f, endRadius = 4f;
            float Radius = ((float)Z.Z - startFloor) * endRadius + (endFloor - (float)Z.Z) * startRadius;
            Radius = Radius / (endFloor - startFloor);
            foreach(Cell C in Z.GetCells())
            {
                if (C.CosmeticDistanceTo(center) > Radius)
                {
                    continue;
                }
                C.RemoveObjects((GameObject o) => true).ForEach(delegate(GameObject o)
                {
                    o.Obliterate();
                });
                C.Clear();
                C.AddObject("Pit");
            }
            Z.FireEvent("FirmPitEdges");
            ZoneBuilderSandbox.EnsureAllVoidsConnected(Z, pathWithNoise: true);
            return true;
        }
   }
}
This creates the zone shown below: