SHORTTUTORIAL: How Pawns Think - roxxploxx/RimWorldModGuide GitHub Wiki

In Progress


The AI of RimWorld is a web of classes and hierarchies of nodes. ThinkTrees are a hierarchy of nodes that calculate priorities based upon game conditions, to select the best current job. Each Pawn determines what to do in it's Pawn_JobTracker (i.e. Pawn.tracker), identifying a Job and using a JobDriver to specify every detail of that job in a list of Toils.

ThinkTrees

You can find ThinkTreeDefs XML files in the ThinkTreeDefs folder for Animal, Humanlike and Mechanoid. There are also files for subtrees used across these. ThinkTreeDefs have a string defName, ThinkNode thinkRoot, string insertTag and int insertPriority. The defName is useful for cross referencing these nodes. So, you could add in a <ThinkTreeDef> entry outside this hierarchy by referencing that defName via the tag. The thinkRoot stores other sub nodes (i.e. List<ThinkNodes> subNodes) and it's purpose is to select among these sub nodes for a valid ThinkResult. This ThinkResult stores the Job and the ThinkNode that asked for it.

Of importance for modders are the <insertTag>. If you create a ThinkTreeDef of your own, you can specify an <insertTag> like this, <insertTag>Humanlike_PostMain</insertTag>, to insert your new ThinkTreeDef into the existing ThinkTree.

Jobs and JobDrivers and Toils

A Job holds the JobDef and Pawn that does it, to make the Pawn do something in the game, that the Pawn or the player thought was a good idea (i.e. the ThinkTrees computed it). The Job though is really only a container to track how something gets done. The JobDriver class, whose Type is specified by the <driverTag> in <JobDef>, is where the actual definition of how to do the job is done. It is broken down into individual Toils that are completed one at a time, in order. There are many types of Toils so you'll have to look through them later.

Create a Job : Study Wizardry

Create the JobDef "JobDef_Wizardry.xml"

Ok, so you're a Wizard Larry. And Larry wants to study up on his spells. So, let's create a job to do just that at a Wizard Alcove.

<?xml version="1.0" encoding="utf-8" ?>
<Defs>
    <JobDef>
        <defName>UM_StudyWizardryJob</defName>
	    <driverClass>UnificaMagica.JobDriver_StudyWizardry</driverClass>
	    <reportString>studying wizardry.</reportString> 
    </JobDef>
</Defs>

This section of XML create a new JobDef and says there is a class in the namespace "UnificaMagica" (my Mod) that will list the Toils to get it done.

Create the JobDriver "JobDriver_StudyWizardry.cs"

adapted from Jecrell's "Star Wars - The Force" mod.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Verse;
using Verse.AI;
using RimWorld;

namespace UnificaMagica
{
    public class JobDriver_StudyWizardry : JobDriver
    {
        private Rot4 faceDir;

        // This class generates a list of Toils (a new one each time as per the 'yield' command)
        // that are managed by the JobDriver in the Pawn_JobTracker.
        [DebuggerHidden]
        protected override IEnumerable<Toil> MakeNewToils()
        {
            yield return Toils_Reserve.Reserve(TargetIndex.A, 1);
            yield return Toils_Goto.GotoCell(TargetIndex.A, PathEndMode.OnCell);
            yield return new Toil
            {
                initAction = delegate
                {
                    this.faceDir = ((!this.CurJob.def.faceDir.IsValid) ? Rot4.Random : this.CurJob.def.faceDir);

                },
                tickAction = delegate
                {
                    this.pawn.Drawer.rotator.FaceCell(this.pawn.Position + this.faceDir.FacingCell);
                    this.pawn.GainComfortFromCellIfPossible();
                    if (this.pawn.TryGetComp<CompAbilityUserWizard>() != null)
                    {
                        CompAbilityUserWizard wizardComp = this.pawn.GetComp<CompAbilityUserWizard>();

                        ... do stuff ...
                        
                        if (! stop condition )
                        {
                            ... do stuff ...
                        else {
                            this.EndJobWith(JobCondition.Succeeded);
                        }
                    }
                },
                defaultCompleteMode = ToilCompleteMode.Delay,
                defaultDuration = 1800
            };
        }

        public override void ExposeData()
        {
            base.ExposeData();
            Scribe_Values.Look<Rot4>(ref this.faceDir, "faceDir", default(Rot4), false);
        }
    }
}
⚠️ **GitHub.com Fallback** ⚠️