-
Notifications
You must be signed in to change notification settings - Fork 1
5) Toils
If you've made it this far, congratulations -- and thank you. Welcome to the nitty-gritty. Before we continue, ask yourself this: Do I really need to write or edit my own Toil? If you came here casually, you most likely don't. If your end-goal isn't to make a job, there's almost certainly some better way to achieve what you want. Since you're still here, let's get started.
As complex as the rest of the job system was, Toils are enjoyably simple at their core. They're reminiscent of the old GOTO style programming of BASIC. The MakeNewToils() IEnumerable in the JobDriver follows them step by step, so Toils are made really explicit and detailed. It's the core of programming: breaking problems down into small steps and solving each of them one by one. As such, you won't find much explanation of the Toils here. If you're this deep down, you can read the decompiled code and find that a lot of them are self-explanatory.
This article will deal with the parts of the toil that aren't self-explanatory - or at least weren't for me.
- Almost everything in the MakeNewToils() IEnumerable has to be a Toil, but Toils don't have to be Toils. You can turn a Toil into a delegate, like in the below example.
- All Toils are evaluated only once: when the job is started. Conditionals such as if-statements will only be evaluated at job start time, even if your job loops. Log.Messages directly inside the enumerator happen instantly. Delegates wait their turn.
- You don't have to do anything inside a Toil. You can end the job inside a delegate, or abuse the queueing system to start a next one. Like in the example below.
Toil takeThing = new Toil
{
initAction = () =>
{
Pawn actor = this.pawn;
Thing thing = actor.CurJob.GetTarget(TargetIndex.A).Thing;
Toils_Haul.ErrorCheckForCarry(actor, thing);
int num = Mathf.Min(thing.stackCount, MassUtility.CountToPickUpUntilOverEncumbered(actor, thing));
if (num <= 0)
{
Job haul = HaulAIUtility.HaulToStorageJob(actor, thing);
if (haul?.TryMakePreToilReservations(actor) ?? false)
{
actor.jobs.jobQueue.EnqueueFirst(haul, new JobTag?(JobTag.Misc));
}
actor.jobs.curDriver.JumpToToil(wait);
}
else
{
actor.inventory.GetDirectlyHeldThings().TryAdd(thing.SplitOff(num), true);
}
}
};
yield return takeThing;
- JobDriver_DoBill is the most complex collection of Toils, and documented source code is supplied with the game. JobDriver_InteractAnimal is another semi-clever example.
- Usually a Toil takes a minimum of a frame to activate (I think this is partially to avoid hardlock loops.) the
atomicWithPrevious
flag basically means that this does something instantly and doesn't really take time, so it should be executed in the same frame. - Toils_JobTransforms looks daunting, but if you read and understood the TargetIndex article, things should be clear by now. It's for messing with the TargetIndex.A and B and extracting the next thing from the list. By default it's First In, First Out - but since you're here I'll assume you know how to manipulate a List to do your bidding.
- Toils_Jump is darned handy, and complex Jobs will be using them a lot. You can extract Toils and put them in their own method to reduce code. Take the Toil to jump to as a parameter, and use
actor.jobs.curDriver.JumpToToil(PARAMETER);
to jump back in line. Check the available options, and don't be afraid to implement your own. Example here. - If you're in the middle of a toil loop and you've reached a completed/end state, you need an EndCurrentJob to get out of it. If you're at the end of the Toil, you don't need an EndCurrentJob, but it doesn't hurt.
- Devmode has an option to
Toggle Job Logging
. It's highly recommend. This will help debugging. There are even mods out there (RD Forced Slowdown) that make the game go slower instead of faster. Or roll your own. - Not all Jobs get registered in the
Toggle Job Logging
. Something likeDropUnusedInventory
has a JobGiver and a JobDriver, but it doesn't return a job. It just goes and does something. - Remember when I said you can't directly assign a TargetIndex? You totally can.
curJob.SetTarget(TargetIndex, Thing);
- You can play sounds, throw motes, do whatever. (
JobDriver_AffectRoof
is a nice example.) Have fun. Make it look cool. Good luck out there!