Modding Tutorials/Linking XML and C

From RimWorld Wiki
Revision as of 12:08, 11 February 2019 by Mehni (talk | contribs) (Created page with "{{BackToTutorials}} Writing custom code is all well and good if you can't force RimWorld to use it! Here are three ways of linking y...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Modding Tutorials

Writing custom code is all well and good if you can't force RimWorld to use it! Here are three ways of linking your Code to the XML and vice versa.

A classy design pattern

RimWorld leans heavily on Object Oriented Programming and will often expose the specific class of a Def to the XML. They're often called a workerClass, thingClass, but obviously can be named anything. Keep a look out for these, they're sometimes subtle.

Examples

<BiomeDef>
  <defName>BorealForest</defName>
  <workerClass>BiomeWorker_BorealForest</workerClass>
</BiomeDef>
<inspectorTabs>
  <li>ITab_Art</li>
</inspectorTabs>

A "Classy" pattern

It is possible to overwrite the default class associated with a Def (or field in a def) by specifying the Class by attribute. The most common usage of this you've undoubtedly seen before is in Components:

<comps>
  <li Class="CompProperties_Forbiddable"/>
</comps>

This class override is more widely applicable than just comps. It's possible to overwrite or specify almost any type you wish. Keep Compatibility in mind when doing this: in the end each object can only be one Class.

Example

<!-- Generate faction base -->
<GenStepDef Class = "MyNameSpace.MyCustomGenStepDefClass"> <!-- Overwrite the GenStepDef Class -->
  <defName>Settlement</defName>
  <order>400</order>
  <genStep Class="GenStep_Settlement"> <!-- you can overwrite almost any type you wish. -->
    <count>1</count>
    <nearMapCenter>true</nearMapCenter>
  </genStep>
  <modExtensions>
    <li Class="MyNameSpace.MyCustomModExtension"> <!-- Aah, the classic li Class. -->
       <isACustomGenStepDefClassAndAModExtensionOverkill>false</isACustomGenStepDefClassAndAModExtensionOverkill>
    </li>
  </modExtensions>
</GenStepDef>

You define yourself

Another possibility is to supply your own type in the XML. The most well-known example of this is Alienraces. The ThingDef_AlienRace inherits from regular ThingDef, and adds the alienRace tag to it. The alienRace tag then holds more information, a sample of which is shown below.

One benefit of this over the annotation method above (the "Classy" pattern) is that the added control and restriction of scope is increased compatibility with over mods. It's theoretically also easier to further subclass the AlienRace def by annotation.

While most implementations have limited themselves to Defs, there is no known technical reason to believe this pattern can't be applied to fields directly.

Example

<AlienRace.ThingDef_AlienRace ParentName="BasePawn">
  <defName>ExampleAlien</defName>
  <label>Alien for example</label>
  <description>This alien is really special.. because it is used as an example</description>
  <!-- regular ThingDef tags go here. -->
  <alienRace>
      <!-- specific alien stuff goes inside the alienRace tag. -->
      <generalSettings>

      </generalSettings>
      <raceRestriction>

      </raceRestriction>
  </alienRace>
</AlienRace.ThingDef_AlienRace>

Serialising custom classes

It may sometimes be required to read/write your own datatype to XML. While we all love long strings with comma separated values, there are many cases where this is not a good idea. For that, RimWorld offers a LoadDataFromXmlCustom method. When RimWorld comes across your type during load, and the class defining that type contains the LoadDataFromXmlCustom, RimWorld will use that method to parse the XML. This is done through the magic of reflection.

Note that this method fails on types that can't have a constructor. In practice, this means you can't serialise structs.

//example taken from https://github.com/LocoNeko/RoadsOfTheRim/blob/master/Source/DefModExtension_RotR_RoadDef.cs
public class RotR_cost
{
    public string name;
    public int count;

    public void LoadDataFromXmlCustom(XmlNode xmlRoot)
    {
        if (xmlRoot.ChildNodes.Count != 1)
        {
            Log.Error("Misconfigured RotR_cost: " + xmlRoot.OuterXml, false);
            return;
        }
        name = xmlRoot.Name;
        count = (int)ParseHelper.FromString(xmlRoot.FirstChild.Value, typeof(int));
    }
}

Common issues

You need to specify your exact Namespace.Class. There are no two ways around it. You also need to supply RimWorld with the assembly containing the type and namespace specified.

See also

Hello World - For when your code shouldn't be linked to XML, but does need to be bootstrapped.