Difference between revisions of "Modding Tutorials/Custom Comp Classes"

From RimWorld Wiki
Jump to navigation Jump to search
(More complicated example...)
 
(9 intermediate revisions by 3 users not shown)
Line 1: Line 1:
Creating a custom comp class is a convenient way to add new functionality to RimWorld.
+
{{BackToTutorials}}
 +
Creating a custom comp class is a convenient way to add new functionality to RimWorld. Comps aren't a formal concept in the code: they're a design pattern used in different places. They allow for a more modular approach to adding functionality to different objects.
  
== Prerequisites ==
+
=The types of Components=
* You need to have [[Modding Tutorials/Setting up a solution|set up your editor and environment]]
+
These are the places where the Comps design pattern is used, each with differing behaviour suited for their respective area. From most specific to most generic:
* You need to know [[Modding Tutorials/Writing custom code|how to write custom code]]
+
==HediffComp==
* You should probably understand Defs at least somewhat.
+
A relatively simple Comp for adding more complex behaviour to Hediffs.
  
== def (xml) and C# structure ==
+
==ThingComp==
 +
{{Main|Modding Tutorials/ThingComp}}
 +
A very powerful Component that is tied to a specific Thing. These are often used to store data, give special functionality to the Thing they're tied to and are one of the building blocks of RimWorld modding and RimWorld in general. While not as powerful as a [[Modding Tutorials/Def classes|fully custom class]], they provide plenty of functionality for a lot of general use cases without compatibility issues.
  
=== Setup, Defs, and Classes ===
+
==WorldObjectComp==
You will have some custom def and it will have something like this:
+
Like a ThingComp, but for WorldObjects.
==== Defs (xml) ====
 
  <ThingDef ...>
 
    ...
 
    ...
 
    <comps>
 
      <<nowiki>li</nowiki> Class="MyNamespace.MyCompProperties">
 
        <myCustomCompProperty>some value</myCustomCompProperty>
 
        <mySecondCompProp>4</mySecondCompProp>
 
      </<nowiki>li</nowiki>>
 
      <<nowiki>li</nowiki>>
 
        ''<<nowiki>!--</nowiki> this is kind of like <tag>MN.MyCustomTag</tag>:-->''
 
        <compClass>MyNamespace.MyCustomThingComp</compClass>
 
      </<nowiki>li</nowiki>>
 
    </comps>
 
  </ThingDef>
 
==== C# ====
 
  namespace MyNamespace ''// For example, LWM.ModName - by using your ''
 
                        ''//  handle/name/etc, you almost certainly guarantee uniqueness''
 
  {
 
  '''//////////// <<nowiki>li</nowiki> Class="MyNamespace.MyCompProperties"> ////////////'''
 
  public class MyCompProperties : CompProperties ''// Name this as you wish, of course''
 
  {
 
    public Properties() {
 
      this.compClass = typeof(MyNamespace.MyLinkedCompThing); ''// rename as appropriate''
 
    }
 
    public string myCustomCompProperty; ''// Name matches def, of course''
 
    public int mySecondCompProp = 1; ''// Can set default values''
 
  }
 
 
 
  ''// this ThingComp is used to actually access the comp property defined above''
 
  '''''// this is not "<compClass>MyNamespace.MyCustomThingComp</compClass>"'''''
 
  public class MyLinkedCompThing : ThingComp
 
  {
 
    public string myCustomCompProperty
 
    {
 
      get
 
      {
 
        return ((MyCompProperties)this.props).myCustomCompProperty;
 
      }
 
    }
 
    public int mySecondCompProperty ''// Have to get all the names right''
 
    { get  { return ((MyCompProperties)this.props).mySecondCompProperty; } } ''//etc''
 
  }
 
  '''//////////// <compClass>MyNamespace.MyCustomThingComp</compClass> ////////////'''
 
  public class MyCustomThingComp : ThingComp
 
  {
 
    public override void CompTick()
 
    {
 
      // do stuff
 
    }
 
    public override void CompTickRare() //etc
 
    public override ... // Check out Verse/ThingComp.cs for more ideas
 
  }
 
  } // end MyNamespace
 
  
=== Accessing your Comps ===
+
==MapComponent==
Just setting up your custom comps doesn't do you a lot of good if you can't access them!
+
{{Main|Modding Tutorials/GameComponent}}
 +
Much like a ThingComp, except these exist at the Map level. They're most useful for keeping tracks of multiple things at once, storing data, and can serve as a coordinator or general managing entity.
  
==== Accessing CompProperty directly ====
+
==WorldComponent==
This is the "hard" way, but doable if you want:
+
{{Main|Modding Tutorials/GameComponent}}
<source lang="C#">
+
Similar to a MapComponent, but lives on the World level.
    int importantProperty = ((MyCompProperties)((ThingWithComps)myObject)
 
                            .GetComp<MyLinkedCompThing>().props).mySecondCompProp;
 
    //    The "(ThingWithComps)" cast may or may not be needed
 
</source>
 
  
==== Use the "get" method we created ====
+
==GameComponent==
Easier approach:
+
{{Main|Modding Tutorials/GameComponent}}
<source lang="C#">
+
Similar to a WorldComponent, but lives at the Game level.
    // real world example:
 
    SlotGroup slotGroup = GetSlotGroupInEarlierCode();
 
    MyLinkedCompThing comp = ((ThingWithComps)slotGroup.parent).GetComp<MyLinkedCompThing>();
 
    if (comp != null) {
 
        string s = comp.myCustomCompProperty;
 
        int x = otherObject.GetComp<MyLinkedCompThing>().mySecondCompProp; // be sure it exists, etc
 
    } else { /* not a thing with my comp, etc */ }
 
</source>
 
  
=== More Complicated ===
+
The distinction between a GameComponent and a WorldComponent might not be too obvious, but a GameComponent gets instantiated when a new Game is started:
You can do a lot of complicated things...
+
* Upon start of the tutorial
<source lang="xml">
+
* When the player starts the Scenario Configuration (rolling for colonists)
    <comps>
+
* When a save is loaded from the main menu
      <li Class="MyNS.Properties>
 
        <myThingFilters>
 
          <li><!-- These are a list, see? -->
 
            <filter>
 
              <categories>
 
                <li>Apparel</li>
 
              </categories>
 
            </filter>
 
          </li>
 
          <li>
 
            <filter>
 
              <categories>
 
                <li>Drugs</li>
 
              </categories>
 
              <thingDefs>
 
                <li>ChunkSlagSteel</li>
 
              </thingDefs>
 
            </filter>
 
          </li>
 
        </myThingFilters>
 
      </li>
 
    </comps>
 
</source>
 
  
And then
+
==StorytellerComp==
 +
These are a specific type of Component that determines the behaviour of the storyteller.
  
<source lang="C#">
+
=Which one to use=
    namespace MyNS {
+
Use whatever is most appropriate, really. Does it deal with a Pawn's health? HediffComp. Is it functionality at Thing level? ThingComp. Does it have to do with two pawns, or multiple items on a map? Probably a MapComponent, or maybe a WorldComponent.
    public class Properties : CompProperties {
 
      public Properties() { this.compClass=typeof(ComplicatedComp); }
 
      public List<ThingFilter> myThingFilters = new List<ThingFilter>;
 
    }
 
    public class ComplicatedComp : ThingComp {
 
      // set up getter as before:
 
      public List<ThingFilter> myThingFilters { get { return ((Properties)this.props).myThingFilters;
 
   
 
    public class MyWeirdCode {
 
      // etc
 
        List<ThingFilter> listOfThingFilters = (ThingWithComps)thing.GetComp<ComplicatedComp>().myThingFilters;
 
        // why you would want a list of thing filters is beyond me?
 
        // what could you even do with it?
 
        // but you can have it.
 
    }
 
</source>
 
  
=== Cautions, traps, etc ===
+
[[Category:Modding tutorials]][[Category:Modding]]
If you have the same comp in an abstract (XML) def and attempt to redefine it in a (XML) child def, it will get counted twice.  It's possible to get around this in the code, if you want to have default comps:
 
 
 
  <ThingDef Name=ParentWithDefault ... Abstract=true>
 
    ...
 
    <comps>
 
      <l<nowiki>i</nowiki> Class="MyCompPropertiesWithDefault">
 
        <myValue>3</myValue><!--default!-->
 
      </l<nowiki>i</nowiki>>
 
    </comps>
 
  </ThingDef>
 
  <ThingDef ParentName="ParentWihtDefault">
 
    ...
 
    <comps>
 
      <l<nowiki>i</nowiki> Class="MyCompPropertiesWithDefault">
 
        <myValue>5</myValue><!-- override default!-->
 
      </<nowiki>l</nowiki>i>
 
    </comps>
 
  </ThingDef>
 
 
 
 
 
  public class MyLinkedCompThing : ThingComp
 
  {
 
    public string myCustomCompProperty //etc
 
   
 
    public override void Initialize (CompProperties props) {
 
      base.Initialize(props);
 
      // Remove duplicate entries and ensure the last entry is the only one left
 
      //  This allows a default abstract def with the comp
 
      //  and child def to change the comp value:
 
      MyCompProprtiesWithDefault[] list = this.parent.GetComps<MyCompPropertiesWithDefault>().ToArray();
 
      // Remove everything but the last entry; harmless if only one entry:
 
      for (var i = 0; i < list.Length-1; i++)
 
      {
 
        this.parent.AllComps.Remove(list[i]);
 
      }
 
    }
 
 
 
  ///etc
 
  }
 
 
 
[[Category:Modding tutorials]][[Category:Modding]][[Category:Defs]]
 

Latest revision as of 00:43, 12 September 2021

Modding Tutorials
Creating a custom comp class is a convenient way to add new functionality to RimWorld. Comps aren't a formal concept in the code: they're a design pattern used in different places. They allow for a more modular approach to adding functionality to different objects.

The types of Components[edit]

These are the places where the Comps design pattern is used, each with differing behaviour suited for their respective area. From most specific to most generic:

HediffComp[edit]

A relatively simple Comp for adding more complex behaviour to Hediffs.

ThingComp[edit]

A very powerful Component that is tied to a specific Thing. These are often used to store data, give special functionality to the Thing they're tied to and are one of the building blocks of RimWorld modding and RimWorld in general. While not as powerful as a fully custom class, they provide plenty of functionality for a lot of general use cases without compatibility issues.

WorldObjectComp[edit]

Like a ThingComp, but for WorldObjects.

MapComponent[edit]

Much like a ThingComp, except these exist at the Map level. They're most useful for keeping tracks of multiple things at once, storing data, and can serve as a coordinator or general managing entity.

WorldComponent[edit]

Similar to a MapComponent, but lives on the World level.

GameComponent[edit]

Similar to a WorldComponent, but lives at the Game level.

The distinction between a GameComponent and a WorldComponent might not be too obvious, but a GameComponent gets instantiated when a new Game is started:

  • Upon start of the tutorial
  • When the player starts the Scenario Configuration (rolling for colonists)
  • When a save is loaded from the main menu

StorytellerComp[edit]

These are a specific type of Component that determines the behaviour of the storyteller.

Which one to use[edit]

Use whatever is most appropriate, really. Does it deal with a Pawn's health? HediffComp. Is it functionality at Thing level? ThingComp. Does it have to do with two pawns, or multiple items on a map? Probably a MapComponent, or maybe a WorldComponent.