[Tutorial](Alpha 12d) How to make pawn do job. From ThinkTree to Toil

Started by BBream, October 02, 2015, 04:30:02 PM

Previous topic - Next topic

BBream

I think many modder don't know how to make pawn do their job. So I have made this tutorial for them.

Prerequisite
You should know how to make dll file. I recommend to do this tutorial made by Haplo.
[Tutorial] (Alpha 11) How to make a .dll-mod (Power Generation)
And you need to know to write C# code.

Purpose
This turorial explain how pawn do thier action with overall. I'll explain how pawn decide their action and how modder can define the action pawn do. So I want other modder to make their own action easily.

What do we do?
we will make human put silver into their inventory if they don't have it. I want human to be more greedy. So lets make this mod.

Overview
This is overview. I'll explain from where finding job is started in to where definition place pawn should do job.
1. Race_Humanlike, ThinkTree
2. ThinkNode, JobGiver
3. JobDriver, Toil

1. Race_Humanlike, ThinkTree
Before I start it, I'll explain how pawn decide their action.
First, see this Race_Humanlike.xml in Core/Defs/ThingDefs.

In Core/Defs/ThingDefs/Race_Humanlike.xml:

  <ThingDef ParentName="BasePawn">
    <defName>Human</defName>
    <label>human</label>
...
    <race>
      <thinkTree>Humanlike</thinkTree>
...
    </race>


You can see the <thinkTree>Humanlike</thinkTree> in <race>. This is first definition for humanlike's thought. Every pawn has their own thinkTree so they do different action each race.
So human will determind what to do as Humanlike thinkTree defined.

Next, see this Humanlike.xml in Core/Defs/ThinkTreeDefs. This file define how pawn has humanlike thinktree should act.

In Core/Defs/ThinkTreeDefs/Humanlike.xml:

<ThinkTreeDef>
<defName>Humanlike</defName>
<thinkRoot Class="ThinkNode_Priority">
<subNodes>
        <li Class="ThinkNode_Subtree">
          <treeDef>Downed</treeDef>
        </li>
        <!-- Burning response -->
<li Class="ThinkNode_Subtree">
<treeDef>BurningResponse</treeDef>
</li>
...
        <!-- Insertion hook for modders -->
        <li Class="ThinkNode_SubtreesByTag">
          <insertTag>Humanlike_PreMain</insertTag>
        </li>
...


You can see a lot of definition. it even define what they do if they are burning. Some ThinkNode has own subTree like real tree's branch. Lets see the 'BurningResponse' in SubTree_Misc.xml

In Core/Defs/ThinkTreeDefs/SubTree_Misc.xml:

  <!--============= Burning ============-->
  <ThinkTreeDef>
<defName>BurningResponse</defName>
<thinkRoot Class="ThinkNode_ConditionalBurning">
<subNodes>
<li Class="ThinkNode_Priority">
<subNodes>
<li Class="JobGiver_ExtinguishSelf" />
<li Class="JobGiver_RunRandom">
              <maxDanger>Deadly</maxDanger>
            </li>
</subNodes>
</li>
</subNodes>
</thinkRoot>
  </ThinkTreeDef>


You can see another thinkTree. This node has simple logic.

1. Check humanlike is burning
2. if pawn is burning, then pawn do act as JobGiver_ExtinguishSelf is defined.
3.But if JobGiver_ExtinguishSelf don't give job, pawn will do act as JobGiver_RunRandom  is defined.

Although we don't know what job both exactly give, we can estimate it will do job like their name.
So thinkTree is ended in JobGiver has job. If no job, thinkTree will advance next line.
We see a part of thinkTree and look around how it give job to pawn. But we cannot modify humanlike thinkTree. Instead, Tynan give other subTree like burningResponse for modder.

In MyMod/Defs/ThinkTreeDefs/SubTrees_Misc.xml:

...
  <!--============= Test of insertion hooks ============-->
  <!-- If you remove the comments around this, it will inject
      this AI control node into all ThinkTrees where there is an
      insertion hook seeking a matching InsertTag.
     
      If there are several injections, they'll be ordered by priorty (highest first).
     
      This example just makes colonists beat on each other, but modders
      can add any behavior using these and they'll all work together.-->
  <!--
  <ThinkTreeDef>
    <defName>InsertHookTest</defName>
    <insertTag>Humanlike_PostBroken</insertTag>
    <insertPriority>100</insertPriority>
    <thinkRoot Class="JobGiver_Berserk" />
  </ThinkTreeDef>
  -->

</ThinkTrees>


You can see end of SubTrees_Misc.xml. This is subTree for modder. So lets start to make tutorial mod.

I want colonist get silver after being ended to pack food. So lets find packing food.

In MyMod/Defs/ThinkTreeDefs/Humanlike.xml:

...
            <!-- Pack food if not hungry-->
            <li Class="ThinkNode_ConditionalNeedAbove">
              <need>Food</need>
              <threshold>0.6</threshold>
              <subNodes>
                <li Class="JobGiver_PackFood" />
              </subNodes>
            </li> 
</subNodes>
</li>

        <!-- Behavior from traits -->
        <li Class="ThinkNode_TraitBehaviors" />

        <!-- Insertion hook for modders -->
        <li Class="ThinkNode_SubtreesByTag">
          <insertTag>Humanlike_PreMain</insertTag>
        </li>


You can see these code in line number 167 and you can find ThinkNode_SubtreesByTag has Humanlike_PreMain tag. we will put tutorial subTree by adding subTree in SubTrees_Misc.xml.

This is steps of making own SubTrees_Misc.xml
1. Copy core's SubTrees_Misc.xml in your mod folder.
2. Open the xml file and delete every code except burningResponse and InsertHookTest def
we will learn from burningResponse subTree.
3. Define tutorial subTree. Let me explain what it is.

In MyMod/Defs/ThinkTreeDefs/SubTrees_Misc.xml:

  <ThinkTreeDef>
    <defName>PackSilver</defName>
    <insertTag>Humanlike_PreMain</insertTag>
    <insertPriority>100</insertPriority>
    <thinkRoot Class="GreedyHuman.ThinkNode_ConditionalHasSilver">
<invert>true</invert>
<subNodes>
<li Class="ThinkNode_Priority">
<subNodes>
<li Class="GreedyHuman.JobGiver_PackSilver" />
</subNodes>
</li>
</subNodes>
    </thinkRoot>
</ThinkTreeDef>


It looks like burningResponse subTree but you can see the different line.

...
    <insertTag>Humanlike_PreMain</insertTag>
    <insertPriority>100</insertPriority>
    <thinkRoot Class="GreedyHuman.ThinkNode_ConditionalHasSilver">
   <invert>true</invert>
   <subNodes>
      <li Class="ThinkNode_Priority">
         <subNodes>
            <li Class="GreedyHuman.JobGiver_PackSilver" />
...

This will act with this logic:
1. Check human has silver in inventory.
2. If human has no silver then human do job as JobGiver_PackSilver is defined.

Let me explain what code means.
<insertTag> means which tag it is inserted.
So we need to insert it Humanlike_PreMain tag.
<insertPriority> means its priority.
If you want to insert some subTree then it need to have priority. Suppose we want to insert pack apparel then typically colonist would get silver rather than apparel. So you can give this value for priority.
<thinkRoot Class="GreedyHuman.ThinkNode_ConditionalHasSilver"> is name what we define in dll code and GreedyHuman is namespace. It will check human has silver and if human has silver it will do job as JobGiver_PackSilver is defined.
But you think it looks wrong? Right, we need to check human has 'no' silver. So we need to invert this condition and need it:
<invert>true</invert>

At this point, we have our own subTree for packing silver. But pawn don't know yet how and when they do job. Lets define ThinkNode_ConditionalHasSilver and JobGiver_PackSilver.

2. ThinkNode, JobGiver
We define subTree for greedy human but pawn still don't know how and when they do job. For this, we should define ThinkNode_ConditionalHasSilver and JobGiver_PackSilver in dll code.
I suppose you already know how to write C# code. So I don't explain about it.

In MyMod/Source/GreedyHuman/ThinkNode_ConditionalHasSilver.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using RimWorld;
using Verse;
using Verse.AI;

namespace GreedyHuman
{
    public class ThinkNode_ConditionalHasSilver : ThinkNode_Conditional
    {
        protected override bool Satisfied(Pawn pawn)
        {
            if (pawn.inventory != null && pawn.inventory.container.Any(thing => thing.def == ThingDefOf.Silver))
                return true;
            return false;
        }
    }
}


This class check human has silver. In ThinkNode_Conditional class, it apply invert value so you don't worry about it.

In MyMod/Source/GreedyHuman/JobGiver_PackSilver.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using RimWorld;
using Verse;
using Verse.AI;

namespace GreedyHuman
{
    public class JobGiver_PackSilver : ThinkNode_JobGiver
    {
        private const int maxSearchDist = 30;

        protected override Job TryGiveTerminalJob(Pawn pawn)
        {
            Predicate<Thing> predicate = thing => !thing.IsForbidden(pawn.Faction) && ReservationUtility.CanReserveAndReach(pawn, thing, PathEndMode.ClosestTouch, Danger.Some);
            Thing silver = GenClosest.ClosestThingReachable(pawn.Position, ThingRequest.ForDef(ThingDefOf.Silver), PathEndMode.ClosestTouch, TraverseParms.For(TraverseMode.ByPawn), maxSearchDist, predicate);
            if (silver != null)
            {
                Job jobNew = new Job(DefDatabase<JobDef>.GetNamed("PutInInventory"), silver);
                return jobNew;
            }
            return (Job)null;
        }
    }
}

This JobGiver would find closest silver(not forbidden and can reserve and reach) in 30 cell distance and if it find then it will give job to human. But if it cannot find silver then it will return no job and human would find other job.

So human have just been given job named PutInInventory. But human don't know how do job named PutInInventory exactly. So we need to define it. it is define in JobDriver.

3. JobDriver, Toil
For defining JobDriver, we need to write xml code and dll code.

1. xml code
You can find Jobs_Misc.xml for watching JobDef in Core/Defs/JobDefs.

In Core/Defs/JobDefs/Jobs_Misc.xml:

<JobDefs>

  <!--========= General ============-->

  <JobDef>
    <defName>TakeInventory</defName>
    <driverClass>JobDriver_TakeInventory</driverClass>
    <reportString>Taking TargetA.</reportString>
  </JobDef>
...

You can see many jobDef and this is 'TakeInventory' jobDef. It define what class exactly define job and reportString. And we need one named 'PutInInventory'.


  <JobDef>
    <defName>PutInInventory</defName>
    <driverClass>GreedyHuman.JobDriver_PutInInventory</driverClass>
    <reportString>Putting TargetA. in Inventory</reportString>
  </JobDef>


2. dll code
This is essence of job. In JobDriver, it define what exactly pawn should do. So it is very important. I'll explain it one by one.

public override string GetReport()
This would report to player what pawn is doing. You can see this report in Inspector Tab.

So it should be return proper string for letting player know what pawn is doing.

protected override IEnumerable<Toil> MakeNewToils()
In this method, it runs toil step by step. But you should write code carefully because it has own sequence.

I'll show the sequence with example.

This example is JobDriver_Kill.cs and you can find it in RimworldGameFolder/Source/Verse/AI/JobDrivers/Casting.

In JobDriver_Kill.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Verse;

namespace Verse.AI{
public class JobDriver_Kill : JobDriver
{
//Constants
private const TargetIndex VictimInd = TargetIndex.A;

protected override IEnumerable<Toil> MakeNewToils()
{
this.EndOnDespawned( VictimInd, JobCondition.Succeeded );

yield return Toils_Reserve.Reserve(VictimInd );

yield return Toils_Combat.TrySetJobToUseAttackVerb();

Toil gotoCastPos = Toils_Combat.GotoCastPosition( VictimInd );
yield return gotoCastPos;

Toil jumpIfCannotHit =  Toils_Jump.JumpIfTargetNotHittable( VictimInd, gotoCastPos );
yield return jumpIfCannotHit;

yield return Toils_Combat.CastVerb( VictimInd );

yield return Toils_Jump.Jump( jumpIfCannotHit );
}
}}


This is sequence of running MakeNewToils().
1. Setup work.
this.EndOnDespawned( VictimInd, JobCondition.Succeeded );
Toil gotoCastPos = Toils_Combat.GotoCastPosition( VictimInd );
Toil jumpIfCannotHit =  Toils_Jump.JumpIfTargetNotHittable( VictimInd, gotoCastPos );

It is setup work before starting toil. Real job would be run in each toil in order. Important thing is that you should not write your definition of job in out of toil. If you do that, JobDriver is typically not worked. So I strongly recommend to use toil for definition.

2. Run toil step by step.
This looks like assembly. It has jump statement so you can define job like program. Toil is one line of code and you can jump other toil if you want. So Toils_*** class is very useful. But for jumping toil, you should make its instance in setup work and you can use instance name as Label for jumping.

And it would check condition setting up in setup work. In this example, if target is despawned, then job would be ended with Succeeded Job condition. It would check every tick time and it is useful for ending job.

And now we have just learned about JobDriver a little. So lets start to make own JobDriver.
For study, I make jobDriver complicated. I hope you study a lot function of JobDriver and Toil.
You can find a lot of example in RimWorldGameFolder/Source/Verse/AI/JobDrivers and JobDrivers/Toils. I attach it in MyMod/Source so you can learn from it.

In JobDriver_PutInInventory:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using RimWorld;
using Verse;
using Verse.AI;

namespace GreedyHuman
{
    public class JobDriver_PutInInventory : JobDriver
    {
        //Constants
        private const TargetIndex SilverInd = TargetIndex.A;
        private const int TickStaring = 600;

        private bool hasGreedyEye;
        private int tickTime;

        public JobDriver_PutInInventory() : base()
        {
            hasGreedyEye = false;
            tickTime = 0;
        }

        public override string GetReport()
        {
            Thing silver = TargetThingA;
            string repString = CurJob.def.reportString.Translate(silver.LabelCap);

            if (hasGreedyEye)
                repString = "Picking up " + Translator.Translate(silver.LabelCap) + " with greedy eye.";

            return repString;
        }

        protected override IEnumerable<Toil> MakeNewToils()
        {
            //Set fail conditions
            this.FailOnDestroyed(SilverInd);
            if (!TargetThingA.IsForbidden(pawn))
                this.FailOnForbidden(SilverInd);

            Toil reserveTargetA = Toils_Reserve.Reserve(SilverInd);
            yield return reserveTargetA;

            yield return Toils_Goto.GotoThing(SilverInd, PathEndMode.ClosestTouch);

            Toil toilPickUp = new Toil();
            toilPickUp.initAction = () =>
                {
                    tickTime = 0;
                    hasGreedyEye = true;
                };
            toilPickUp.AddPreTickAction(() =>
                {
                    if (tickTime > TickStaring)
                        ReadyForNextToil();
                });
            toilPickUp.tickAction = () =>
                {
                    tickTime++;
                };
            toilPickUp.AddFinishAction(() =>
                {
                    hasGreedyEye = false;
                    if (pawn.inventory.container.TryAdd(TargetThingA))
                    {
                        //For avoiding error message
                        TargetThingA.holder = pawn.inventory.GetContainer();
                        TargetThingA.holder.owner = pawn.inventory;
                    }
                });
            toilPickUp.WithEffect(EffecterDefOf.ConstructDirt, SilverInd);
            toilPickUp.defaultCompleteMode = ToilCompleteMode.Never;

            yield return toilPickUp;

            //yield return Toils_MyToils.JumpIfSameItemNearby(SilverInd, reserveTargetA);
        }
    }
}

This JobDriver examine how to use toil with tick. Human would go to silver and pick it up for putting it in inventory after 600 ticks.
Toils_MyToils.JumpIfSameItemNearby check same item nearby but it makes error. I'll leave it for showing how to define your own Toils class but please don't use it because of error.



[attachment deleted due to age]

mipen

Great tutorial! This should really help people, this can be a tricky topic. Nice work!

BBream

Purpose
You have learned about making Job with overall. In this time, I'll explain how to define work for colonist. As you know colonist do their work for colony. You can add your own work in mod.

What do we do?
We will make breeding work. I want colonist to love each other and be more closer than before with children.

Overview
1. ThinkTree, WorkTypeDefs
2. WorkGiverDefs
3. WorkGiver

1. ThinkTree, WorkTypeDefs
Work is started in JobGiver_Work. You can find it in Humanlike ThinkTree.

In Core/Defs/ThinkTreeDefs/Humanlike.xml:

...
            <!-- Emergency work -->
            <li Class="JobGiver_Work">
              <emergency>true</emergency>
            </li>
...
        <!-- Main colonist behavior core-->
        <li Class="ThinkNode_PrioritySorter">
          <subNodes>
            <li Class="JobGiver_GetFood"/>
            <li Class="JobGiver_GetRest"/>
            <li Class="JobGiver_GetJoy"/>
            <li Class="JobGiver_Work"/>
          </subNodes>
</li>

You can see two JobGiver_Work. First one is for emergency work like extinguishing fire. This work is set emergency value so it would give job first. Second one is for normal work. It would give job in priority order.
You have just learned where work job is given. Now lets see where work settings is defined and work is defined.



This is work settings. You can see this in game and it is defined WorkTypes.xml in Core/Defs/WorkTypeDefs.

In Core/Defs/WorkTypeDefs/WorkTypes.xml:

...
<WorkTypeDef>
<defName>Warden</defName>
<labelShort>Warden</labelShort>
<pawnLabel>Warden</pawnLabel>
<gerundLabel>Wardening</gerundLabel>
<description>Wardens manage, feed, chat with, and recruit prisoners.</description>
<verb>Handle</verb>
<naturalPriority>1100</naturalPriority>
<requireCapableColonist>true</requireCapableColonist>
<relevantSkills>
<li>Social</li>
</relevantSkills>
<workTags>
<li>Social</li>
</workTags>
</WorkTypeDef>
...

You can see 15 defs in this file. In WorkTypeDef, there are a lot of value. Lets see what we need

<naturalPriority> is workType's priority so all warden type work have 1100 priority. This mean Warden type work would be done before starting Cooking type work.
<relevantSkills> is
<workTags> is related to colonist's character. If colonist has incapable of Social then they wouldn't do any Wardon type work.
<relevantSkills>, <description> is displayed if you lead mouse in specific work active button. It would explain what skill is related and what work is.
<scanThings>, <scanCells> cannot see here but it is important. <scanThings> has defaultly true. It tell the JobGiver_Work to notice what kind of workGiver itself. And according to this value you need to change method we override.
I can't explain exactly other value but it looks no problem if you just copy and modify a little bit.
So we make breeding mod but use other word because colonist is human.

In MyMod/Defs/WorkTypeDefs/WorkTypes.xml

...
<WorkTypeDef>
<defName>Date</defName>
    <labelShort>Date</labelShort>
<pawnLabel>Date</pawnLabel>
<gerundLabel>Dating</gerundLabel>
<description>Date with other gender</description>
<verb>Date with</verb>
<naturalPriority>1125</naturalPriority>
<requireCapableColonist>true</requireCapableColonist>
<relevantSkills>
<li>Social</li>
</relevantSkills>
<workTags>
<li>Social</li>
</workTags>
</WorkTypeDef>
...

We already know that for breeding we need to s...special date with other gender. I know it looks kind of Joy and I don't mean I deny homosexual. It is just word.
If you add this WorkTypeDef, then you will see work tab in game. And I want this work to give higher priority than Warden so I give 1125 value.  Next we will define WorkGiverDefs.

2. WorkGiverDefs
We have just defined work type. it is some kind of category and it categorize same kind of work. For example, Handling categorize kind of animal handling work. So if player disable it then colonist would not do any kind of animal work like Slaughter, Milk, Shear and Tame. And these have higher priority than all kind of cook work except work that emergency value is true.
And lets see WorkGiverDef.

In Core/Defs/WorkGiverDefs/WorkGiver.xml:

...
  <WorkGiverDef>
    <defName>Warden</defName>
    <giverClass>WorkGiver_Warden</giverClass>
    <workType>Warden</workType>
    <verb>warden</verb>
    <gerund>wardening</gerund>
    <requiredCapacities>
      <li>Manipulation</li>
      <li>Talking</li>
    </requiredCapacities>
  </WorkGiverDef>
...
  <WorkGiverDef>
    <defName>Slaughter</defName>
    <giverClass>WorkGiver_Slaughter</giverClass>
    <workType>Handling</workType>
    <verb>slaughter</verb>
    <gerund>slaughtering</gerund>
    <priorityInType>100</priorityInType>
    <requiredCapacities>
      <li>Manipulation</li>
    </requiredCapacities>
  </WorkGiverDef>
...

You can also see a lot of value.
<giverClass> is Class name in dll code. We will define it later.
<workType> is workType involved. As I explained, it categorize work.
<priorityInType> is priority in type. It means slaughter has higher priority than milk work.
<requiredCapacities> is required capacities. For example, if colonist lose talking capacity because of injury, then they can't do Warden work.
And lets define our own WorkGiverDef.

In MyMod/Defs/WorkGiverDefs/WorkGiver.xml

  <WorkGiverDef>
    <defName>Breed</defName>
    <giverClass>ColonyBreeding.WorkGiver_Breed</giverClass>
    <workType>Date</workType>
    <verb>breed</verb>
    <gerund>breeding</gerund>
    <requiredCapacities>
      <li>Manipulation</li>
      <li>Talking</li>
    </requiredCapacities>
  </WorkGiverDef>

You can see Breed WorkGiverDef and it is in Date work type. It don't need to have priorityInType value because it don't have work that has same work type.
We have just defined Breed in Date work type. Now we need to define WorkGiver_Breed in dll code.

3. WorkGiver
WorkGiver look like JobGiver but it has more method for searching job. So you need to define it more detail than JobGiver. There are three kind of WorkGiver according to inherit class and def value:
1. WorkGiver that has no scan.
2. WorkGiver_Scanner that has scanThings value is true. (Default)
3. WorkGiver_Scanner that has scanCells value is true.

And we are going to make second one.
Before we start to make it, lets see how JobGiver_Work check these kinds of WorkGiver has job.

In Assembly-CSharp.dll/RimWorld/JobGiver_Work.cs:

if (workGiver.MissingRequiredCapacity(workGiver.pawn) == null)
     {
          if (!workGiver.ShouldSkip(workGiver.pawn))
          {
                ...
                Job job = workGiver.NonScanJob(workGiver.pawn);
                if (job != null)
                  return job;
                workGiver_Scanner.scanner = workGiver as WorkGiver_Scanner;
                if (workGiver_Scanner.scanner != null)
                {
                  if (workGiver.def.scanThings)
IEnumerable<Thing> customGlobalSearchSet = workGiver_Scanner.scanner.PotentialWorkThingsGlobal(workGiver_Scanner.pawn);
                    Predicate<Thing> validator = predicate;
                    Thing thing = GenClosest.ClosestThingReachable(workGiver_Scanner.pawn.Position, workGiver_Scanner.scanner.PotentialWorkThingRequest, workGiver_Scanner.scanner.PathEndMode, TraverseParms.For(workGiver_Scanner.pawn, Danger.Deadly, TraverseMode.ByPawn, false), 9999f, validator, customGlobalSearchSet, workGiver_Scanner.scanner.LocalRegionsToScanFirst, customGlobalSearchSet != null);
                  if (workGiver_Scanner.def.scanCells)
                      ...
              if (targetInfo.IsValid)
              {
                workGiver_Scanner.pawn.mindState.lastGivenWorkType = workGiver.def.workType;
                if (targetInfo.HasThing)
                  return workGiverScanner.JobOnThing(workGiver_Scanner.pawn, targetInfo.Thing);
                return workGiverScanner.JobOnCell(workGiver_Scanner.pawn, targetInfo.Cell);
              }

It looks very complicated so let me briefly explain it.
1. Check MissingRequiredCapacity(workGiver.pawn)
2. Check ShouldSkip(workGiver.pawn)
3. If workGiver is non scanner then give job to pawn.
4. If workGiver has scanThings true value, Find closest thing with predicate named HasJobOnThing
5. If workGiver has scanCells true value, Find closest cell with predicate named HasJobOnCell
6. If target is found give job to pawn. (JobOnThing or JobOnCell)

And this WorkGiver is for breeding.
In MyMod/Source/WorkGiver_Breed.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using RimWorld;
using Verse;
using Verse.AI;

namespace ColonyBreeding
{
    public class WorkGiver_Breed : WorkGiver_Scanner
    {
        public IEnumerable<Thing> Pawns(Pawn pawn) { return Find.ListerThings.ThingsInGroup(ThingRequestGroup.Pawn); }

        public override IEnumerable<Thing> PotentialWorkThingsGlobal(Pawn pawn)
        {
            return Pawns(pawn) as IEnumerable<Thing>;
        }

        public override bool HasJobOnThing(Pawn pawn, Thing t)
        {
            //Check thing is pawn.
            Pawn mate = t as Pawn;
            if (mate == null)
                return false;
           
            //Check mate and pawn has different gender.
            Pawn male = (pawn.gender == Gender.Male) ? pawn : (mate.gender == Gender.Male) ? mate : null;
            Pawn female = (pawn.gender == Gender.Female) ? pawn : (mate.gender == Gender.Female) ? mate : null;
            if (male == null || female == null)
                return false;
           
            return mate.Faction == pawn.Faction && !mate.Downed && mate.RaceProps.Humanlike && pawn.RaceProps.Humanlike
                && PawnUtility.CasualInterruptibleNow(pawn) && PawnUtility.CasualInterruptibleNow(mate)
                && ReservationUtility.CanReserve(pawn, mate)
                && PawnUtility.FertileMateTarget(male, female);
        }

        public override bool ShouldSkip(Pawn pawn)
        {
            //DebugLogMessage(pawn);
            return !Pawns(pawn).Any(mate => mate is Pawn && (pawn.RaceProps.Humanlike && ((Pawn)mate).RaceProps.Humanlike) &&((Pawn)mate).gender != pawn.gender && ((Pawn)mate).Faction == pawn.Faction);
        }

        public override Job JobOnThing(Pawn pawn, Thing t)
        {
            Pawn mate = t as Pawn;
            if (mate == null)
            {
                Log.Error(pawn.LabelCap + " report: mate is not pawn.");
                return (Job)null;
            }
            IntVec3 breedCell;
            if (!RCellFinder.TryFindSkygazeCell(pawn.Position, pawn, out breedCell))
                return (Job)null;

            Job jobNew = new Job(DefDatabase<JobDef>.GetNamed("Breed"), mate, breedCell);
            return jobNew;
        }

        private void DebugLogMessage(Pawn pawn)
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.AppendLine(pawn.LabelCap + " report: ");

            Pawn male;
            Pawn female;
            foreach (Pawn mate in Pawns(pawn))
            {
                if (pawn.RaceProps.Humanlike && mate.RaceProps.Humanlike)
                    continue;
                male = (pawn.gender == Gender.Male) ? pawn : mate;
                female = (pawn.gender == Gender.Female) ? pawn : mate;

                stringBuilder.AppendLine("Name: " + pawn.LabelCap + ", " + mate.LabelCap);
                stringBuilder.Append("\tGender: " + pawn.gender + ", " + mate.gender);
                stringBuilder.Append("\tFaction: " + pawn.Faction + ", " + mate.Faction);
                stringBuilder.Append("\tDowned: " + pawn.Downed + ", " + mate.Downed);
                stringBuilder.Append("\tHumanlike: " + pawn.RaceProps.Humanlike + ", " + mate.RaceProps.Humanlike);
                stringBuilder.Append("\tCanReserve: " + ReservationUtility.CanReserve(pawn, mate));
                stringBuilder.Append("\tCasualInterruptibleNow: " + PawnUtility.CasualInterruptibleNow(pawn) + ", " + PawnUtility.CasualInterruptibleNow(mate));
                stringBuilder.Append("\tFertileMateTarget: " + PawnUtility.FertileMateTarget(male, female));
                stringBuilder.AppendLine();
            }
            Log.Message(stringBuilder.ToString());
        }
    }
}


It give breeding job if there is target. I'll not explain about JobDriver this thread. And I have attached other WorkGiver in zip file so you can learn from it.

[attachment deleted due to age]


BBream

Quote from: mipen on October 02, 2015, 04:46:19 PM
Great tutorial! This should really help people, this can be a tricky topic. Nice work!

I hope it is helpful for modder. I suffered from insufficient document.

1000101

This is brilliant, thank-you.

There are a couple experiments I've wanted to try involving more in-depth pawn AI but was too lazy to do all the reverse engineering.  :D

*bookmarked*
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

skullywag

Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

1000101

(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

isistoy

<Stay on the scene like a State machine>

TheGentlmen