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

From RimWorld Wiki
Jump to navigation Jump to search
m (PigeonGuru moved page Custom Comp Classes to Modding Tutorials/Custom Comp Classes: group modding tutorials under same namespace)
(Work in progress)
Line 2: Line 2:
 
Creating a custom comp class is a convenient way to add new functionality to RimWorld.
 
Creating a custom comp class is a convenient way to add new functionality to RimWorld.
  
== Prerequisites ==
+
'''Benefits''':<br/>
 +
<nowiki>+</nowiki> Are very well supported by RimWorld.<br/>
 +
<nowiki>+</nowiki> Exposes (part of its) functionality to XML.<br/>
 +
<nowiki>+</nowiki> Compatible with savefiles, other mods, you name it.</br>
 +
<nowiki>+</nowiki> Does not come with the compatibility issues and pitfalls of creating a custom Def class.<br/>
 +
<nowiki>+</nowiki> Can save data.<br/>
 +
<nowiki>+</nowiki> Can access its parent.<br/>
 +
<nowiki>+</nowiki> Certain functionality gets automatically called RimWorld.<br/>
 +
 
 +
'''Downsides''':<br>
 +
<nowiki>-</nowiki> Only works on ThingWithComps.<br/>
 +
<nowiki>-</nowiki> Requires a few extra steps to expose their functionality to XML.<br/>
 +
<nowiki>-</nowiki> Not suited for every type of functionaliy.<br/>
 +
 
 +
== Requirements ==
 
* You need to have [[Modding Tutorials/Setting up a solution|set up your editor and environment]]
 
* You need to have [[Modding Tutorials/Setting up a solution|set up your editor and environment]]
 
* You need to know [[Modding Tutorials/Writing custom code|how to write custom code]]
 
* You need to know [[Modding Tutorials/Writing custom code|how to write custom code]]
 
* You should probably understand Defs at least somewhat.
 
* You should probably understand Defs at least somewhat.
  
== def (xml) and C# structure ==
+
== What you'll learn ==
 +
What ThingComps are, when and how to use them. Since ThingComps are so ubiquitous and practical, every method you can override is also included, along with a short blurb of what it does.
 +
 
 +
== Setting up ==
 +
To implement a ThingComp, we need to inherit from the ''ThingComp'' class. This tutorial assumes you'll want to expose your Comp's functionality to XML, for that we need to inherit from ''CompProperties'' as well.
 +
 
 +
== The Code ==
 +
RimWorld will initialise your Comp like thus:
 +
# First the ThingMaker will create a new instance of a Def, and call this a Thing.
 +
# ThingMaker will then call PostMake() on the instance of Thing (in our case, a ThingWithComps).
 +
# PostMake() will call ThingWithComps.InitializeComps().
 +
# For every entry in the Def's Comp list, ThingWithComps.InitializeComps will create a new instance of each compClass (in every case, something that inherits from ThingComp)
 +
# ThingWithComps.InitializeComps will set the parent of the ThingComp.
 +
# ThingWithComps.InitializeComps calls the Initialize() method of the ThingComp instance.
 +
# ThingComp.Initialize() will set the CompProperties, with the properties as defined in the Def.
 +
 
 +
Note that this is [https://en.wikipedia.org/wiki/Object-oriented_programming Object Oriented Programming]: the (JIT) compiler first looks for overridden methods to call before calling the parent.
 +
 
 +
==An Example==
 +
The below is a simple example. We'll use this later on.
 +
<source lang = "csharp">
 +
using Verse;
 +
using System;
 +
 
 +
namespace MyExampleNamespace
 +
{
 +
    public class MyExampleComp : ThingComp
 +
    {
 +
        public override void CompTick()
 +
        {
 +
            Log.Error("Let's error on every tick!");
 +
        }
 +
 
 +
        /// <summary>
 +
        /// By default this.props returns the base CompProperties class.
 +
        /// You can get this.props and cast it everywhere you use it,
 +
        /// or you create a Getter like this, which casts once and returns it.
 +
        /// Careful of case sensitivity!
 +
        /// </summary>
 +
        public MyExampleCompProperties Props => (MyExampleCompProperties)this.props;
 +
 
 +
        public bool ExampleBool => Props.myExampleBool;
 +
    }
 +
 
 +
    public class MyExampleCompProperties : CompProperties
 +
    {
 +
        public bool myExampleBool;
 +
        public float myExampleFloat;
 +
 
 +
        /// <summary>
 +
        /// These constructors aren't strictly required if the compClass is set in the XML.
 +
        /// </summary>
 +
        public MyExampleCompProperties()
 +
        {
 +
            this.compClass = typeof(MyExampleComp);
 +
        }
 +
 
 +
        public MyExampleCompProperties(Type compClass) : base(compClass)
 +
        {
 +
            this.compClass = compClass;
 +
        }
 +
    }
 +
}
 +
</source>
 +
 
 +
===ThingComp===
 +
It does not make sense to override every method listed here. It therefor goes without saying you should only override those methods need.
 +
 
 +
==== Initialize(CompProperties props)====
 +
Called once when the ThingComp is instantiated, and called during loading. Used to, well, initialise the props.
 +
 
 +
==== ReceiveCompSignal(string signal)====
 +
A string based signalling system that allows for communication between comps.
 +
 +
==== PostExposeData====
 +
Used for saving and loading data. Runs after the ThingWithComps ExposeData.
 +
 
 +
==== PostSpawnSetup(bool respawningAfterLoad)====
 +
Similar to Initialize(), but be aware that there's a difference between "getting initialised" and "spawning". A ThingWithComps can be initialised without getting spawned. Useful for setting up Map- and Thing-related stuff.
 +
 
 +
==== PostDeSpawn(Map map)====
 +
Gets called after despawning. Useful for cleaning up. Note: getting despawned doesn't mean destroyed. Think: caravans, minifying, etc.
 +
 
 +
==== PostDestroy(DestroyMode mode, Map previousMap)====
 +
Gets called after being destroyed. Useful for cleaning up.
 +
 +
==== CompTick====
 +
Runs once every [[Time|Tick]], but only if the TickerType of the parent is TickerType Normal.
 +
 
 +
==== CompTickRare====
 +
Runs once every [[Time|TickRare]], but only if the TickerType of the parent is TickerType Rare.
 +
 
 +
There is no CompTickLong.
 +
 
 +
==== PostPreApplyDamage(DamageInfo dinfo, out bool absorbed)====
 +
Runs before damage gets applied to the parent. The bool sets if damage was absorbed or not. Full damage absorption or nothing.
 +
 
 +
==== PostPostApplyDamage(DamageInfo dinfo, float totalDamageDealt)====
 +
Runs after damage gets applied to the parent.
 +
 
 +
==== PostDraw====
 +
Called every frame. Only use this for drawing. Do not put game logic in this.
 +
 
 +
==== PostDrawExtraSelectionOverlays====
 +
Called every frame, if the parent is selected. Used by things like the DeepScanner. Only use this for drawing. Do not put game logic in this.
 +
 
 +
==== PostPrintOnto(SectionLayer layer)====
 +
Not a clue. Power uses it.
 +
 
 +
==== CompPrintForPowerGrid(SectionLayer layer)====
 +
Not a clue. Power uses it.
 +
 
 +
==== PreAbsorbStack(Thing otherStack, int count)====
 +
Used before the parent absorbs ''count'' amount of ''otherStack''. Useful for averaging out values of your Comp. e.g. FoodPoisoning.
 +
 
 +
==== PostSplitOff(Thing piece)====
 +
The opposite of PreAbsorbStack.
 +
 
 +
==== TransformLabel(string label)====
 +
Returns a string. Neurotrainers use this to identify what type of Neurotrainer they are.
 +
 
 +
==== CompGetGizmosExtra====
 +
Returns an IEnumerable<Gizmo>. Lets you add [[Modding Tutorials/Gizmos|Gizmos]] to the thing. Gizmos are the actions buttons you see when you select something, like the temperature setting buttons.
 +
 
 +
==== AllowStackWith(Thing other)====
 +
Returns a boolean that determines whether or not two Things can stack.
 +
 
 +
==== CompInspectStringExtra====
 +
Returns a string. Adds extra things to the selection box.
 +
 
 +
==== GetDescriptionPart====
 +
Returns a string. Adds extra things to the selection box.
 +
 
 +
==== CompFloatMenuOptions(Pawn selPawn)====
 +
Returns an IEnumerable<FloatMenuOption>. Lets you add a FloatMenuOption to the thing.
 +
 
 +
==== PrePreTraded(TradeAction action, Pawn playerNegotiator, ITrader trader)====
 +
Runs before the trade is completed. Useful for making Pawns very attached to a Thing, then make them hate the Pawn that sells it.
 +
 
 +
==== PostIngested(Pawn ingester)====
 +
Runs after the Thing is eaten.
 +
 
 +
==== PostPostGeneratedForTrader(TraderKindDef trader, int forTile, Faction forFaction)====
 +
Runs after it's generated for a trader. Used by CompQuality.
 +
 
 +
==== Notify_SignalReceived(Signal signal)====
 +
The other part of the string based inter comp communication network.
 +
 
 +
====ToString====
 +
For debugging.
 +
 
 +
===CompProperties===
 +
 
 +
====CompProperties()====
 +
A constructor.
 +
====CompProperties(Type compClass)====
 +
A constructor which takes a parameter.
 +
 
 +
====DrawGhost(IntVec3 center, Rot4 rot, ThingDef thingDef, Color ghostCol, AltitudeLayer drawAltitude)====
 +
Used by Fire, and other cases where you'd not recognise the Blueprint without the Comp.
 +
 
 +
====ConfigErrors(ThingDef parentDef)====
 +
Returns an IEnumerable<string> with config errors. Don't forget to ''yield return'' every error in the ''base''.
 +
 
 +
====ResolveReferences(ThingDef parentDef)====
 +
Not a clue. Used for Resolving references, obviously, but dunno the use.
 +
 
 +
==== SpecialDisplayStats(StatRequest req)====
 +
Returns an IEnumerable<StatDrawEntry>. Useful for when your Comp adds extra stats.
 +
 
 +
===XML===
 +
 
  
 
=== Setup, Defs, and Classes ===
 
=== Setup, Defs, and Classes ===
Line 87: Line 272:
 
         int x = otherObject.GetComp<MyLinkedCompThing>().mySecondCompProp; // be sure it exists, etc
 
         int x = otherObject.GetComp<MyLinkedCompThing>().mySecondCompProp; // be sure it exists, etc
 
     } else { /* not a thing with my comp, etc */ }
 
     } else { /* not a thing with my comp, etc */ }
</source>
 
 
=== More Complicated ===
 
You can do a lot of complicated things...
 
<source lang="xml">
 
    <comps>
 
      <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
 
 
<source lang="C#">
 
    namespace MyNS {
 
    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>
 
</source>
  
 
=== Cautions, traps, etc ===
 
=== Cautions, traps, etc ===
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:
+
* 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. To get around this in XML, inherit from a newly defined Parent without the Comp. It's '''not''' recommended to add the Inherit="False" tag to the comps tag, as that gets rid of all Comps. While you can get around it in code, a newly defined parent is cleaner (and less effort).
 
+
* Not every Thing is a ThingWithComps! Only Things that are a ThingWithComps (or subclass thereof) have Comps. Chunks are a notable Thing that aren't ThingWithComps.
  <ThingDef Name=ParentWithDefault ... Abstract=true>
+
* Casting to ThingWithComps is needlessly expensive and explicit casting can lead to InvalidCastExceptions. If it's a Thing you suspect is a ThingWithComps, you can:
    ...
+
<source lang ="csharp">
    <comps>
+
Thing thing;
      <l<nowiki>i</nowiki> Class="MyCompPropertiesWithDefault">
+
MyExampleComp comp = thing.TryGetComp<MyExampleComp>();
        <myValue>3</myValue><!--default!-->
+
if (comp != null)
      </l<nowiki>i</nowiki>>
+
{
    </comps>
+
     //do stuff
  </ThingDef>
+
}</source>
  <ThingDef ParentName="ParentWihtDefault">
+
* Ticking! Make sure the TickerType and your Tick method matches. If the ThingWithComp has the Normal TickerType, CompTickRare() will never get called.
    ...
 
    <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]]
 
[[Category:Modding tutorials]][[Category:Modding]][[Category:Defs]]

Revision as of 16:34, 31 January 2019

Modding Tutorials
Creating a custom comp class is a convenient way to add new functionality to RimWorld.

Benefits:
+ Are very well supported by RimWorld.
+ Exposes (part of its) functionality to XML.
+ Compatible with savefiles, other mods, you name it.
+ Does not come with the compatibility issues and pitfalls of creating a custom Def class.
+ Can save data.
+ Can access its parent.
+ Certain functionality gets automatically called RimWorld.

Downsides:
- Only works on ThingWithComps.
- Requires a few extra steps to expose their functionality to XML.
- Not suited for every type of functionaliy.

Requirements

What you'll learn

What ThingComps are, when and how to use them. Since ThingComps are so ubiquitous and practical, every method you can override is also included, along with a short blurb of what it does.

Setting up

To implement a ThingComp, we need to inherit from the ThingComp class. This tutorial assumes you'll want to expose your Comp's functionality to XML, for that we need to inherit from CompProperties as well.

The Code

RimWorld will initialise your Comp like thus:

  1. First the ThingMaker will create a new instance of a Def, and call this a Thing.
  2. ThingMaker will then call PostMake() on the instance of Thing (in our case, a ThingWithComps).
  3. PostMake() will call ThingWithComps.InitializeComps().
  4. For every entry in the Def's Comp list, ThingWithComps.InitializeComps will create a new instance of each compClass (in every case, something that inherits from ThingComp)
  5. ThingWithComps.InitializeComps will set the parent of the ThingComp.
  6. ThingWithComps.InitializeComps calls the Initialize() method of the ThingComp instance.
  7. ThingComp.Initialize() will set the CompProperties, with the properties as defined in the Def.

Note that this is Object Oriented Programming: the (JIT) compiler first looks for overridden methods to call before calling the parent.

An Example

The below is a simple example. We'll use this later on.

using Verse;
using System;

namespace MyExampleNamespace
{
    public class MyExampleComp : ThingComp
    {
        public override void CompTick()
        {
            Log.Error("Let's error on every tick!");
        }

        /// <summary>
        /// By default this.props returns the base CompProperties class.
        /// You can get this.props and cast it everywhere you use it, 
        /// or you create a Getter like this, which casts once and returns it.
        /// Careful of case sensitivity!
        /// </summary>
        public MyExampleCompProperties Props => (MyExampleCompProperties)this.props;

        public bool ExampleBool => Props.myExampleBool;
    }

    public class MyExampleCompProperties : CompProperties
    {
        public bool myExampleBool;
        public float myExampleFloat;

        /// <summary>
        /// These constructors aren't strictly required if the compClass is set in the XML.
        /// </summary>
        public MyExampleCompProperties()
        {
            this.compClass = typeof(MyExampleComp);
        }

        public MyExampleCompProperties(Type compClass) : base(compClass)
        {
            this.compClass = compClass;
        }
    }
}

ThingComp

It does not make sense to override every method listed here. It therefor goes without saying you should only override those methods need.

Initialize(CompProperties props)

Called once when the ThingComp is instantiated, and called during loading. Used to, well, initialise the props.

ReceiveCompSignal(string signal)

A string based signalling system that allows for communication between comps.

PostExposeData

Used for saving and loading data. Runs after the ThingWithComps ExposeData.

PostSpawnSetup(bool respawningAfterLoad)

Similar to Initialize(), but be aware that there's a difference between "getting initialised" and "spawning". A ThingWithComps can be initialised without getting spawned. Useful for setting up Map- and Thing-related stuff.

PostDeSpawn(Map map)

Gets called after despawning. Useful for cleaning up. Note: getting despawned doesn't mean destroyed. Think: caravans, minifying, etc.

PostDestroy(DestroyMode mode, Map previousMap)

Gets called after being destroyed. Useful for cleaning up.

CompTick

Runs once every Tick, but only if the TickerType of the parent is TickerType Normal.

CompTickRare

Runs once every TickRare, but only if the TickerType of the parent is TickerType Rare.

There is no CompTickLong.

PostPreApplyDamage(DamageInfo dinfo, out bool absorbed)

Runs before damage gets applied to the parent. The bool sets if damage was absorbed or not. Full damage absorption or nothing.

PostPostApplyDamage(DamageInfo dinfo, float totalDamageDealt)

Runs after damage gets applied to the parent.

PostDraw

Called every frame. Only use this for drawing. Do not put game logic in this.

PostDrawExtraSelectionOverlays

Called every frame, if the parent is selected. Used by things like the DeepScanner. Only use this for drawing. Do not put game logic in this.

PostPrintOnto(SectionLayer layer)

Not a clue. Power uses it.

CompPrintForPowerGrid(SectionLayer layer)

Not a clue. Power uses it.

PreAbsorbStack(Thing otherStack, int count)

Used before the parent absorbs count amount of otherStack. Useful for averaging out values of your Comp. e.g. FoodPoisoning.

PostSplitOff(Thing piece)

The opposite of PreAbsorbStack.

TransformLabel(string label)

Returns a string. Neurotrainers use this to identify what type of Neurotrainer they are.

CompGetGizmosExtra

Returns an IEnumerable<Gizmo>. Lets you add Gizmos to the thing. Gizmos are the actions buttons you see when you select something, like the temperature setting buttons.

AllowStackWith(Thing other)

Returns a boolean that determines whether or not two Things can stack.

CompInspectStringExtra

Returns a string. Adds extra things to the selection box.

GetDescriptionPart

Returns a string. Adds extra things to the selection box.

CompFloatMenuOptions(Pawn selPawn)

Returns an IEnumerable<FloatMenuOption>. Lets you add a FloatMenuOption to the thing.

PrePreTraded(TradeAction action, Pawn playerNegotiator, ITrader trader)

Runs before the trade is completed. Useful for making Pawns very attached to a Thing, then make them hate the Pawn that sells it.

PostIngested(Pawn ingester)

Runs after the Thing is eaten.

PostPostGeneratedForTrader(TraderKindDef trader, int forTile, Faction forFaction)

Runs after it's generated for a trader. Used by CompQuality.

Notify_SignalReceived(Signal signal)

The other part of the string based inter comp communication network.

ToString

For debugging.

CompProperties

CompProperties()

A constructor.

CompProperties(Type compClass)

A constructor which takes a parameter.

DrawGhost(IntVec3 center, Rot4 rot, ThingDef thingDef, Color ghostCol, AltitudeLayer drawAltitude)

Used by Fire, and other cases where you'd not recognise the Blueprint without the Comp.

ConfigErrors(ThingDef parentDef)

Returns an IEnumerable<string> with config errors. Don't forget to yield return every error in the base.

ResolveReferences(ThingDef parentDef)

Not a clue. Used for Resolving references, obviously, but dunno the use.

SpecialDisplayStats(StatRequest req)

Returns an IEnumerable<StatDrawEntry>. Useful for when your Comp adds extra stats.

XML

Setup, Defs, and Classes

You will have some custom def and it will have something like this:

Defs (xml)

 <ThingDef ...>
   ...
   ...
   <comps>
     <li Class="MyNamespace.MyCompProperties">
       <myCustomCompProperty>some value</myCustomCompProperty>
       <mySecondCompProp>4</mySecondCompProp>
     </li>
     <li>
       <!-- this is kind of like <tag>MN.MyCustomTag</tag>:-->
       <compClass>MyNamespace.MyCustomThingComp</compClass>
     </li>
   </comps>
 </ThingDef>

C#

 namespace MyNamespace // For example, LWM.ModName - by using your 
                       //   handle/name/etc, you almost certainly guarantee uniqueness
 {
 //////////// <li 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

Just setting up your custom comps doesn't do you a lot of good if you can't access them!

Accessing CompProperty directly

This is the "hard" way, but doable if you want:

    int importantProperty = ((MyCompProperties)((ThingWithComps)myObject)
                             .GetComp<MyLinkedCompThing>().props).mySecondCompProp;
    //     The "(ThingWithComps)" cast may or may not be needed

Use the "get" method we created

Easier approach:

    // 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 */ }

Cautions, traps, etc

  • 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. To get around this in XML, inherit from a newly defined Parent without the Comp. It's not recommended to add the Inherit="False" tag to the comps tag, as that gets rid of all Comps. While you can get around it in code, a newly defined parent is cleaner (and less effort).
  • Not every Thing is a ThingWithComps! Only Things that are a ThingWithComps (or subclass thereof) have Comps. Chunks are a notable Thing that aren't ThingWithComps.
  • Casting to ThingWithComps is needlessly expensive and explicit casting can lead to InvalidCastExceptions. If it's a Thing you suspect is a ThingWithComps, you can:
Thing thing;
MyExampleComp comp = thing.TryGetComp<MyExampleComp>();
if (comp != null)
{
    //do stuff
}
  • Ticking! Make sure the TickerType and your Tick method matches. If the ThingWithComp has the Normal TickerType, CompTickRare() will never get called.